diff --git a/docs/.anchors.json b/docs/.anchors.json
new file mode 100644
index 000000000000..5cbf872e9dc4
--- /dev/null
+++ b/docs/.anchors.json
@@ -0,0 +1,4174 @@
+{
+ "changelogs/v4.0.0-alpha.1.md": {
+ "version-400-alpha1": [
+ "version-4-0-0-alpha-1"
+ ]
+ },
+ "changelogs/v4.0.0-alpha.2.md": {
+ "version-400-alpha2": [
+ "version-4-0-0-alpha-2"
+ ]
+ },
+ "changelogs/v4.0.0-alpha.3.md": {
+ "version-400-alpha3": [
+ "version-4-0-0-alpha-3"
+ ]
+ },
+ "changelogs/v4.0.0-alpha.4.md": {
+ "version-400-alpha4": [
+ "version-4-0-0-alpha-4"
+ ]
+ },
+ "changelogs/v4.0.0-alpha.5.md": {
+ "version-400-alpha5": [
+ "version-4-0-0-alpha-5"
+ ]
+ },
+ "changelogs/v4.0.0-beta.1.md": {
+ "version-400-beta1": [
+ "version-4-0-0-beta-1"
+ ]
+ },
+ "changelogs/v4.0.0-beta.2.md": {
+ "version-400-beta2": [
+ "version-4-0-0-beta-2"
+ ]
+ },
+ "changelogs/v4.0.0-beta.3.md": {
+ "version-400-beta3": [
+ "version-4-0-0-beta-3"
+ ]
+ },
+ "changelogs/v4.0.0-beta.4.md": {
+ "version-400-beta4": [
+ "version-4-0-0-beta-4"
+ ]
+ },
+ "changelogs/v4.0.0-rc.1.md": {
+ "version-400-rc1": [
+ "version-4-0-0-rc-1"
+ ]
+ },
+ "changelogs/v4.0.0-rc.2.md": {
+ "version-400-rc2": [
+ "version-4-0-0-rc-2"
+ ]
+ },
+ "changelogs/v4.0.0-rc.3.md": {
+ "version-400-rc3": [
+ "version-4-0-0-rc-3"
+ ]
+ },
+ "changelogs/v4.0.0-rc.4.md": {
+ "version-400-rc4": [
+ "version-4-0-0-rc-4"
+ ]
+ },
+ "changelogs/v4.0.0.md": {
+ "version-400": [
+ "version-4-0-0"
+ ]
+ },
+ "changelogs/v4.0.3.md": {
+ "version-403": [
+ "version-4-0-3"
+ ]
+ },
+ "changelogs/v4.0.4.md": {
+ "version-404": [
+ "version-4-0-4"
+ ]
+ },
+ "changelogs/v4.0.5.md": {
+ "version-405": [
+ "version-4-0-5"
+ ]
+ },
+ "changelogs/v4.1.0.md": {
+ "version-410": [
+ "version-4-1-0"
+ ]
+ },
+ "changelogs/v4.1.1.md": {
+ "version-411": [
+ "version-4-1-1"
+ ]
+ },
+ "changelogs/v4.1.2.md": {
+ "version-412": [
+ "version-4-1-2"
+ ]
+ },
+ "changelogs/v4.1.3.md": {
+ "version-413": [
+ "version-4-1-3"
+ ]
+ },
+ "changelogs/v4.1.4.md": {
+ "version-414": [
+ "version-4-1-4"
+ ]
+ },
+ "changelogs/v4.1.5.md": {
+ "version-415": [
+ "version-4-1-5"
+ ]
+ },
+ "changelogs/v4.1.6.md": {
+ "version-416": [
+ "version-4-1-6"
+ ],
+ "validation-changes": [
+ "changelog-v416-validation-changes"
+ ]
+ },
+ "changelogs/v4.1.7.md": {
+ "version-417": [
+ "version-4-1-7"
+ ]
+ },
+ "changelogs/v4.1.8.md": {
+ "version-418": [
+ "version-4-1-8"
+ ]
+ },
+ "changelogs/v4.1.9.md": {
+ "version-419": [
+ "version-4-1-9"
+ ]
+ },
+ "changelogs/v4.2.0.md": {
+ "version-420": [
+ "version-4-2-0"
+ ],
+ "new-improved-auto-routing": [
+ "v420-new-improved-auto-routing"
+ ]
+ },
+ "changelogs/v4.2.1.md": {
+ "version-421": [
+ "version-4-2-1"
+ ]
+ },
+ "changelogs/v4.2.10.md": {
+ "version-4210": [
+ "version-4-2-10"
+ ]
+ },
+ "changelogs/v4.2.11.md": {
+ "version-4211": [
+ "version-4-2-11"
+ ]
+ },
+ "changelogs/v4.2.12.md": {
+ "version-4212": [
+ "version-4-2-12"
+ ]
+ },
+ "changelogs/v4.2.2.md": {
+ "version-422": [
+ "version-4-2-2"
+ ]
+ },
+ "changelogs/v4.2.3.md": {
+ "version-423": [
+ "version-4-2-3"
+ ]
+ },
+ "changelogs/v4.2.4.md": {
+ "version-424": [
+ "version-4-2-4"
+ ]
+ },
+ "changelogs/v4.2.5.md": {
+ "version-425": [
+ "version-4-2-5"
+ ]
+ },
+ "changelogs/v4.2.6.md": {
+ "version-426": [
+ "version-4-2-6"
+ ]
+ },
+ "changelogs/v4.2.7.md": {
+ "version-427": [
+ "version-4-2-7"
+ ]
+ },
+ "changelogs/v4.2.8.md": {
+ "version-428": [
+ "version-4-2-8"
+ ]
+ },
+ "changelogs/v4.2.9.md": {
+ "version-429": [
+ "version-4-2-9"
+ ]
+ },
+ "changelogs/v4.3.0.md": {
+ "version-430": [
+ "version-4-3-0"
+ ],
+ "time": [
+ "v430-time-fix"
+ ],
+ "interface-changes": [
+ "v430-interface-changes"
+ ],
+ "validation-changes": [
+ "v430-validation-changes"
+ ],
+ "multiple-domain-support": [
+ "v430-multiple-domain-support"
+ ]
+ },
+ "changelogs/v4.3.1.md": {
+ "version-431": [
+ "version-4-3-1"
+ ]
+ },
+ "changelogs/v4.3.2.md": {
+ "version-432": [
+ "version-4-3-2"
+ ],
+ "base_url": [
+ "base-url"
+ ],
+ "uri_string": [
+ "v432-uri-string",
+ "uri-string"
+ ]
+ },
+ "changelogs/v4.3.3.md": {
+ "version-433": [
+ "version-4-3-3"
+ ]
+ },
+ "changelogs/v4.3.4.md": {
+ "version-434": [
+ "version-4-3-4"
+ ],
+ "redirect-status-code": [
+ "v434-redirect-status-code"
+ ],
+ "forgemodifycolumn": [
+ "v434-forge-modifycolumn",
+ "forge-modifycolumn"
+ ]
+ },
+ "changelogs/v4.3.5.md": {
+ "version-435": [
+ "version-4-3-5"
+ ]
+ },
+ "changelogs/v4.3.6.md": {
+ "version-436": [
+ "version-4-3-6"
+ ],
+ "validationinterfacecheck": [
+ "validationinterface-check"
+ ],
+ "validationcheck": [
+ "validation-check"
+ ]
+ },
+ "changelogs/v4.3.7.md": {
+ "version-437": [
+ "version-4-3-7"
+ ]
+ },
+ "changelogs/v4.3.8.md": {
+ "version-438": [
+ "version-4-3-8"
+ ]
+ },
+ "changelogs/v4.4.0.md": {
+ "version-440": [
+ "version-4-4-0"
+ ],
+ "urisetsegment-and-non-existent-segment": [
+ "uri-setsegment-and-non-existent-segment"
+ ],
+ "factories": [
+ "v440-factories"
+ ],
+ "codeigniter-and-exit": [
+ "v440-codeigniter-and-exit"
+ ],
+ "site-uri-changes": [
+ "v440-site-uri-changes"
+ ],
+ "interface-changes": [
+ "v440-interface-changes"
+ ],
+ "method-signature-changes": [
+ "v440-method-signature-changes"
+ ],
+ "parameter-type-changes": [
+ "v440-parameter-type-changes"
+ ]
+ },
+ "changelogs/v4.4.1.md": {
+ "version-441": [
+ "version-4-4-1"
+ ]
+ },
+ "changelogs/v4.4.2.md": {
+ "version-442": [
+ "version-4-4-2"
+ ]
+ },
+ "changelogs/v4.4.3.md": {
+ "version-443": [
+ "version-4-4-3"
+ ]
+ },
+ "changelogs/v4.4.4.md": {
+ "version-444": [
+ "version-4-4-4"
+ ],
+ "the-use-of-the-ssl_key-option-in-curlrequest-was-removed": [
+ "the-use-of-the-ssl-key-option-in-curlrequest-was-removed"
+ ]
+ },
+ "changelogs/v4.4.5.md": {
+ "version-445": [
+ "version-4-4-5"
+ ]
+ },
+ "changelogs/v4.4.6.md": {
+ "version-446": [
+ "version-4-4-6"
+ ],
+ "timecreatefromtimestamp": [
+ "time-createfromtimestamp"
+ ]
+ },
+ "changelogs/v4.4.7.md": {
+ "version-447": [
+ "version-4-4-7"
+ ]
+ },
+ "changelogs/v4.4.8.md": {
+ "version-448": [
+ "version-4-4-8"
+ ]
+ },
+ "changelogs/v4.5.0.md": {
+ "version-450": [
+ "version-4-5-0"
+ ],
+ "required-filters": [
+ "v450-required-filters"
+ ],
+ "limit0-behavior": [
+ "v450-query-builder-limit-0-behavior",
+ "limit-0-behavior"
+ ],
+ "findall0-behavior": [
+ "v450-model-findall-limit-0-behavior",
+ "findall-0-behavior"
+ ],
+ "apiresponsetrait": [
+ "api-responsetrait"
+ ],
+ "method-signature-changes": [
+ "v450-method-signature-changes"
+ ],
+ "removed-deprecated-items": [
+ "v450-removed-deprecated-items"
+ ]
+ },
+ "changelogs/v4.5.1.md": {
+ "version-451": [
+ "version-4-5-1"
+ ]
+ },
+ "changelogs/v4.5.2.md": {
+ "version-452": [
+ "version-4-5-2"
+ ]
+ },
+ "changelogs/v4.5.3.md": {
+ "version-453": [
+ "version-4-5-3"
+ ]
+ },
+ "changelogs/v4.5.4.md": {
+ "version-454": [
+ "version-4-5-4"
+ ]
+ },
+ "changelogs/v4.5.5.md": {
+ "version-455": [
+ "version-4-5-5"
+ ]
+ },
+ "changelogs/v4.5.6.md": {
+ "version-456": [
+ "version-4-5-6"
+ ]
+ },
+ "changelogs/v4.5.7.md": {
+ "version-457": [
+ "version-4-5-7"
+ ]
+ },
+ "changelogs/v4.5.8.md": {
+ "version-458": [
+ "version-4-5-8"
+ ]
+ },
+ "changelogs/v4.6.0.md": {
+ "version-460": [
+ "version-4-6-0"
+ ],
+ "exceptions": [
+ "v460-behavior-changes-exceptions"
+ ],
+ "timecreatefromtimestamp": [
+ "time-createfromtimestamp"
+ ],
+ "timesettimestamp": [
+ "time-settimestamp"
+ ],
+ "headers": [
+ "v460-interface-changes"
+ ],
+ "method-signature-changes": [
+ "v460-method-signature-changes"
+ ],
+ "removed-deprecated-items": [
+ "v460-removed-deprecated-items"
+ ]
+ },
+ "changelogs/v4.6.1.md": {
+ "version-461": [
+ "version-4-6-1"
+ ]
+ },
+ "changelogs/v4.6.2.md": {
+ "version-462": [
+ "version-4-6-2"
+ ]
+ },
+ "changelogs/v4.6.3.md": {
+ "version-463": [
+ "version-4-6-3"
+ ]
+ },
+ "changelogs/v4.6.4.md": {
+ "version-464": [
+ "version-4-6-4"
+ ]
+ },
+ "changelogs/v4.6.5.md": {
+ "version-465": [
+ "version-4-6-5"
+ ]
+ },
+ "changelogs/v4.7.0.md": {
+ "version-470": [
+ "version-4-7-0"
+ ],
+ "interface-changes": [
+ "v470-interface-changes"
+ ],
+ "method-signature-changes": [
+ "v470-method-signature-changes"
+ ],
+ "property-signature-changes": [
+ "v470-property-signature-changes"
+ ],
+ "removed-deprecated-items": [
+ "v470-removed-deprecated-items"
+ ]
+ },
+ "changelogs/v4.7.1.md": {
+ "version-471": [
+ "version-4-7-1"
+ ]
+ },
+ "changelogs/v4.7.2.md": {
+ "version-472": [
+ "version-4-7-2"
+ ]
+ },
+ "changelogs/v4.7.3.md": {
+ "version-473": [
+ "version-4-7-3"
+ ]
+ },
+ "changelogs/v4.7.4.md": {
+ "version-474": [
+ "version-4-7-4"
+ ]
+ },
+ "changelogs/v4.7.5.md": {
+ "version-475": [
+ "version-4-7-5"
+ ]
+ },
+ "changelogs/v4.7.6.md": {
+ "version-476": [
+ "version-4-7-6"
+ ]
+ },
+ "changelogs/v4.7.7.md": {
+ "version-477": [
+ "version-4-7-7"
+ ]
+ },
+ "cli/cli_commands.md": {
+ "codeignitercli": [
+ "namespace-CodeIgniter\\CLI"
+ ],
+ "basecommand_1": [
+ "CodeIgniter\\CLI\\BaseCommand"
+ ],
+ "callstring-command-array-params": [
+ "CodeIgniter\\CLI\\BaseCommand::call"
+ ],
+ "showerrorthrowable-e": [
+ "CodeIgniter\\CLI\\BaseCommand::showError"
+ ],
+ "showhelp": [
+ "CodeIgniter\\CLI\\BaseCommand::showHelp"
+ ],
+ "setpadstring-item-int-max-int-extra-2-int-indent-0-string": [
+ "CodeIgniter\\CLI\\BaseCommand::setPad"
+ ],
+ "getpadarray-pad": [
+ "CodeIgniter\\CLI\\BaseCommand::getPad"
+ ]
+ },
+ "cli/cli_controllers.md": {
+ "lets-try-it-hello-world": [
+ "let-s-try-it-hello-world"
+ ],
+ "thats-the-basics": [
+ "that-s-the-basics"
+ ]
+ },
+ "cli/cli_generators.md": {
+ "makecell": [
+ "make-cell"
+ ],
+ "makecommand": [
+ "make-command"
+ ],
+ "makeconfig": [
+ "make-config"
+ ],
+ "makecontroller": [
+ "make-controller"
+ ],
+ "makeentity": [
+ "make-entity"
+ ],
+ "makefilter": [
+ "make-filter"
+ ],
+ "makemodel": [
+ "make-model"
+ ],
+ "makeseeder": [
+ "make-seeder"
+ ],
+ "maketest": [
+ "cli-generators-make-test",
+ "make-test"
+ ],
+ "maketransformer": [
+ "make-transformer"
+ ],
+ "makemigration": [
+ "make-migration"
+ ],
+ "makevalidation": [
+ "make-validation"
+ ]
+ },
+ "cli/cli_library.md": {
+ "promptbymultiplekeys": [
+ "prompt-by-multiple-keys"
+ ],
+ "color": [
+ "cli-library-color"
+ ]
+ },
+ "cli/cli_request.md": {
+ "getoptionkey": [
+ "getoption-key"
+ ]
+ },
+ "cli/cli_signals.md": {
+ "how-pauseresume-works": [
+ "how-pause-resume-works"
+ ],
+ "codeignitercli": [
+ "namespace-CodeIgniter\\CLI"
+ ],
+ "signaltrait": [
+ "CodeIgniter\\CLI\\SignalTrait"
+ ],
+ "registersignalssignals-sigterm-sigint-sighup-sigquit-methodmap": [
+ "CodeIgniter\\CLI\\SignalTrait::registerSignals"
+ ],
+ "isrunning": [
+ "CodeIgniter\\CLI\\SignalTrait::isRunning"
+ ],
+ "shouldterminate": [
+ "CodeIgniter\\CLI\\SignalTrait::shouldTerminate"
+ ],
+ "requesttermination": [
+ "CodeIgniter\\CLI\\SignalTrait::requestTermination"
+ ],
+ "resetstate": [
+ "CodeIgniter\\CLI\\SignalTrait::resetState"
+ ],
+ "withsignalsblockedoperation": [
+ "CodeIgniter\\CLI\\SignalTrait::withSignalsBlocked"
+ ],
+ "aresignalsblocked": [
+ "CodeIgniter\\CLI\\SignalTrait::areSignalsBlocked"
+ ],
+ "mapsignalsignal-method": [
+ "CodeIgniter\\CLI\\SignalTrait::mapSignal"
+ ],
+ "getsignalnamesignal": [
+ "CodeIgniter\\CLI\\SignalTrait::getSignalName"
+ ],
+ "hassignals": [
+ "CodeIgniter\\CLI\\SignalTrait::hasSignals"
+ ],
+ "getsignals": [
+ "CodeIgniter\\CLI\\SignalTrait::getSignals"
+ ],
+ "getprocessstate": [
+ "CodeIgniter\\CLI\\SignalTrait::getProcessState"
+ ],
+ "unregistersignals": [
+ "CodeIgniter\\CLI\\SignalTrait::unregisterSignals"
+ ]
+ },
+ "concepts/autoloader.md": {
+ "namespaces": [
+ "autoloader-namespaces"
+ ],
+ "application-namespace": [
+ "autoloader-application-namespace"
+ ],
+ "filelocator-caching": [
+ "file-locator-caching"
+ ]
+ },
+ "concepts/factories.md": {
+ "loading-classes": [
+ "factories-loading-class"
+ ],
+ "passing-classname-without-namespace": [
+ "factories-passing-classname-without-namespace"
+ ],
+ "passing-fully-qualified-classname": [
+ "factories-passing-fully-qualified-classname"
+ ],
+ "config": [
+ "factories-config"
+ ],
+ "defining-classname-to-be-loaded": [
+ "factories-defining-classname-to-be-loaded"
+ ],
+ "config-caching": [
+ "factories-config-caching"
+ ]
+ },
+ "concepts/security.md": {
+ "a012021-broken-access-control": [
+ "a01-2021-broken-access-control"
+ ],
+ "a022021-cryptographic-failures": [
+ "a02-2021-cryptographic-failures"
+ ],
+ "a032021-injection": [
+ "a03-2021-injection"
+ ],
+ "a042021-insecure-design": [
+ "a04-2021-insecure-design"
+ ],
+ "a052021-security-misconfiguration": [
+ "a05-2021-security-misconfiguration"
+ ],
+ "a062021-vulnerable-and-outdated-components": [
+ "a06-2021-vulnerable-and-outdated-components"
+ ],
+ "a072021-identification-and-authentication-failures": [
+ "a07-2021-identification-and-authentication-failures"
+ ],
+ "a082021-software-and-data-integrity-failures": [
+ "a08-2021-software-and-data-integrity-failures"
+ ],
+ "a092021-security-logging-and-monitoring-failures": [
+ "a09-2021-security-logging-and-monitoring-failures"
+ ],
+ "a102021-server-side-request-forgery-ssrf": [
+ "a10-2021-server-side-request-forgery-ssrf"
+ ],
+ "api12023-broken-object-level-authorization": [
+ "api1-2023-broken-object-level-authorization"
+ ],
+ "api22023-broken-authentication": [
+ "api2-2023-broken-authentication"
+ ],
+ "api32023-broken-object-property-level-authorization": [
+ "api3-2023-broken-object-property-level-authorization"
+ ],
+ "api42023-unrestricted-resource-consumption": [
+ "api4-2023-unrestricted-resource-consumption"
+ ],
+ "api52023-broken-function-level-authorization": [
+ "api5-2023-broken-function-level-authorization"
+ ],
+ "api62023-unrestricted-access-to-sensitive-business-flows": [
+ "api6-2023-unrestricted-access-to-sensitive-business-flows"
+ ],
+ "api72023-server-side-request-forgery": [
+ "api7-2023-server-side-request-forgery"
+ ],
+ "api82023-security-misconfiguration": [
+ "api8-2023-security-misconfiguration"
+ ],
+ "api92023-improper-inventory-management": [
+ "api9-2023-improper-inventory-management"
+ ],
+ "api102023-unsafe-consumption-of-apis": [
+ "api10-2023-unsafe-consumption-of-apis"
+ ]
+ },
+ "concepts/services.md": {
+ "single_service": [
+ "single-service"
+ ]
+ },
+ "concepts/structure.md": {
+ "public": [
+ "application-structure-public"
+ ]
+ },
+ "database/configuration.md": {
+ "configuring-with-env-file": [
+ "database-config-with-env-file"
+ ],
+ "description-of-values": [
+ "database-config-explanation-of-values"
+ ],
+ "encrypt": [
+ "mysqli-encrypt"
+ ]
+ },
+ "database/events.md": {
+ "dbquery": [
+ "database-events-dbquery"
+ ]
+ },
+ "database/metadata.md": {
+ "db-getfielddata": [
+ "db-metadata-getfielddata"
+ ],
+ "db-getindexdata": [
+ "db-metadata-getindexdata"
+ ],
+ "db-getforeignkeydata": [
+ "metadata-getforeignkeydata"
+ ]
+ },
+ "database/queries.md": {
+ "escaping-values": [
+ "database-queries-escaping"
+ ],
+ "1-db-escape": [
+ "database-queries-db-escape",
+ "db-escape"
+ ],
+ "2-db-escapestring": [
+ "db-escapestring"
+ ],
+ "3-db-escapelikestring": [
+ "db-escapelikestring"
+ ],
+ "query-bindings": [
+ "database-queries-query-bindings"
+ ],
+ "close": [
+ "database-queries-stmt-close"
+ ]
+ },
+ "database/query_builder.md": {
+ "select": [
+ "query-builder-select"
+ ],
+ "rawsql": [
+ "query-builder-select-rawsql"
+ ],
+ "subqueries": [
+ "query-builder-from-subquery"
+ ],
+ "builder-join": [
+ "query-builder-join"
+ ],
+ "rawsql_1": [
+ "query-builder-join-rawsql"
+ ],
+ "1-simple-keyvalue-method": [
+ "simple-key-value-method"
+ ],
+ "2-custom-keyvalue-method": [
+ "custom-key-value-method"
+ ],
+ "3-associative-array-method": [
+ "associative-array-method"
+ ],
+ "4-custom-string": [
+ "custom-string"
+ ],
+ "5-rawsql": [
+ "query-builder-where-rawsql",
+ "rawsql"
+ ],
+ "6-subqueries": [
+ "query-builder-where-subquery",
+ "subqueries"
+ ],
+ "1-simple-keyvalue-method_1": [
+ "simple-key-value-method"
+ ],
+ "2-associative-array-method": [
+ "associative-array-method"
+ ],
+ "3-rawsql": [
+ "query-builder-like-rawsql",
+ "rawsql"
+ ],
+ "1-simple-keyvalue-method_2": [
+ "simple-key-value-method"
+ ],
+ "2-associative-array-method_1": [
+ "associative-array-method"
+ ],
+ "union-queries": [
+ "query-builder-union"
+ ],
+ "insertbatch": [
+ "insert-batch-data"
+ ],
+ "upserting-data": [
+ "upsert-data"
+ ],
+ "updatebatch": [
+ "update-batch"
+ ],
+ "deletebatch": [
+ "delete-batch"
+ ],
+ "when": [
+ "db-builder-when"
+ ],
+ "resetting-query-builder": [
+ "ar-caching"
+ ],
+ "codeigniterdatabase": [
+ "namespace-CodeIgniter\\Database"
+ ],
+ "basebuilder": [
+ "CodeIgniter\\Database\\BaseBuilder"
+ ],
+ "db": [
+ "CodeIgniter\\Database\\BaseBuilder::db"
+ ],
+ "resetquery_1": [
+ "CodeIgniter\\Database\\BaseBuilder::resetQuery"
+ ],
+ "countallresultsreset-true": [
+ "CodeIgniter\\Database\\BaseBuilder::countAllResults"
+ ],
+ "countallreset-true": [
+ "CodeIgniter\\Database\\BaseBuilder::countAll"
+ ],
+ "getlimit-null-offset-null-reset-true": [
+ "CodeIgniter\\Database\\BaseBuilder::get"
+ ],
+ "getwherewhere-null-limit-null-offset-null-reset-true": [
+ "CodeIgniter\\Database\\BaseBuilder::getWhere"
+ ],
+ "selectselect-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::select"
+ ],
+ "selectavgselect-alias": [
+ "CodeIgniter\\Database\\BaseBuilder::selectAvg"
+ ],
+ "selectmaxselect-alias": [
+ "CodeIgniter\\Database\\BaseBuilder::selectMax"
+ ],
+ "selectminselect-alias": [
+ "CodeIgniter\\Database\\BaseBuilder::selectMin"
+ ],
+ "selectsumselect-alias": [
+ "CodeIgniter\\Database\\BaseBuilder::selectSum"
+ ],
+ "selectcountselect-alias": [
+ "CodeIgniter\\Database\\BaseBuilder::selectCount"
+ ],
+ "selectsubquerybasebuilder-subquery-string-as": [
+ "CodeIgniter\\Database\\BaseBuilder::selectSubquery"
+ ],
+ "distinctval-true": [
+ "CodeIgniter\\Database\\BaseBuilder::distinct"
+ ],
+ "fromfrom-overwrite-false": [
+ "CodeIgniter\\Database\\BaseBuilder::from"
+ ],
+ "fromsubqueryfrom-alias": [
+ "CodeIgniter\\Database\\BaseBuilder::fromSubquery"
+ ],
+ "setqueryasdataquery-alias-columns-null": [
+ "CodeIgniter\\Database\\BaseBuilder::setQueryAsData"
+ ],
+ "jointable-cond-type-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::join"
+ ],
+ "wherekey-value-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::where"
+ ],
+ "orwherekey-value-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::orWhere"
+ ],
+ "orwhereinkey-null-values-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::orWhereIn"
+ ],
+ "orwherenotinkey-null-values-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::orWhereNotIn"
+ ],
+ "whereinkey-null-values-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::whereIn"
+ ],
+ "wherenotinkey-null-values-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::whereNotIn"
+ ],
+ "groupstart": [
+ "CodeIgniter\\Database\\BaseBuilder::groupStart"
+ ],
+ "orgroupstart": [
+ "CodeIgniter\\Database\\BaseBuilder::orGroupStart"
+ ],
+ "notgroupstart": [
+ "CodeIgniter\\Database\\BaseBuilder::notGroupStart"
+ ],
+ "ornotgroupstart": [
+ "CodeIgniter\\Database\\BaseBuilder::orNotGroupStart"
+ ],
+ "groupend": [
+ "CodeIgniter\\Database\\BaseBuilder::groupEnd"
+ ],
+ "likefield-match-side-both-escape-null-insensitivesearch-false": [
+ "CodeIgniter\\Database\\BaseBuilder::like"
+ ],
+ "orlikefield-match-side-both-escape-null-insensitivesearch-false": [
+ "CodeIgniter\\Database\\BaseBuilder::orLike"
+ ],
+ "notlikefield-match-side-both-escape-null-insensitivesearch-false": [
+ "CodeIgniter\\Database\\BaseBuilder::notLike"
+ ],
+ "ornotlikefield-match-side-both-escape-null-insensitivesearch-false": [
+ "CodeIgniter\\Database\\BaseBuilder::orNotLike"
+ ],
+ "havingkey-value-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::having"
+ ],
+ "orhavingkey-value-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::orHaving"
+ ],
+ "orhavinginkey-null-values-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::orHavingIn"
+ ],
+ "orhavingnotinkey-null-values-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::orHavingNotIn"
+ ],
+ "havinginkey-null-values-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::havingIn"
+ ],
+ "havingnotinkey-null-values-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::havingNotIn"
+ ],
+ "havinglikefield-match-side-both-escape-null-insensitivesearch-false": [
+ "CodeIgniter\\Database\\BaseBuilder::havingLike"
+ ],
+ "orhavinglikefield-match-side-both-escape-null-insensitivesearch-false": [
+ "CodeIgniter\\Database\\BaseBuilder::orHavingLike"
+ ],
+ "nothavinglikefield-match-side-both-escape-null-insensitivesearch-false": [
+ "CodeIgniter\\Database\\BaseBuilder::notHavingLike"
+ ],
+ "ornothavinglikefield-match-side-both-escape-null-insensitivesearch-false": [
+ "CodeIgniter\\Database\\BaseBuilder::orNotHavingLike"
+ ],
+ "havinggroupstart": [
+ "CodeIgniter\\Database\\BaseBuilder::havingGroupStart"
+ ],
+ "orhavinggroupstart": [
+ "CodeIgniter\\Database\\BaseBuilder::orHavingGroupStart"
+ ],
+ "nothavinggroupstart": [
+ "CodeIgniter\\Database\\BaseBuilder::notHavingGroupStart"
+ ],
+ "ornothavinggroupstart": [
+ "CodeIgniter\\Database\\BaseBuilder::orNotHavingGroupStart"
+ ],
+ "havinggroupend": [
+ "CodeIgniter\\Database\\BaseBuilder::havingGroupEnd"
+ ],
+ "groupbyby-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::groupBy"
+ ],
+ "orderbyorderby-direction-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::orderBy"
+ ],
+ "limitvalue-offset-0": [
+ "CodeIgniter\\Database\\BaseBuilder::limit"
+ ],
+ "offsetoffset": [
+ "CodeIgniter\\Database\\BaseBuilder::offset"
+ ],
+ "unionunion": [
+ "CodeIgniter\\Database\\BaseBuilder::union"
+ ],
+ "unionallunion": [
+ "CodeIgniter\\Database\\BaseBuilder::unionAll"
+ ],
+ "setkey-value-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::set"
+ ],
+ "insertset-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::insert"
+ ],
+ "insertbatchset-null-escape-null-batch_size-100": [
+ "CodeIgniter\\Database\\BaseBuilder::insertBatch"
+ ],
+ "setinsertbatchkey-value-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::setInsertBatch"
+ ],
+ "upsertset-null-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::upsert"
+ ],
+ "upsertbatchset-null-escape-null-batch_size-100": [
+ "CodeIgniter\\Database\\BaseBuilder::upsertBatch"
+ ],
+ "updateset-null-where-null-limit-null": [
+ "CodeIgniter\\Database\\BaseBuilder::update"
+ ],
+ "updatebatchset-null-constraints-null-batchsize-100": [
+ "CodeIgniter\\Database\\BaseBuilder::updateBatch"
+ ],
+ "updatefieldsset-addtodefault-false-ignore-null": [
+ "CodeIgniter\\Database\\BaseBuilder::updateFields"
+ ],
+ "onconstraintset": [
+ "CodeIgniter\\Database\\BaseBuilder::onConstraint"
+ ],
+ "setdataset-escape-null-alias": [
+ "CodeIgniter\\Database\\BaseBuilder::setData"
+ ],
+ "setupdatebatchkey-value-escape-null": [
+ "CodeIgniter\\Database\\BaseBuilder::setUpdateBatch"
+ ],
+ "replaceset-null": [
+ "CodeIgniter\\Database\\BaseBuilder::replace"
+ ],
+ "deletewhere-limit-null-reset_data-true": [
+ "CodeIgniter\\Database\\BaseBuilder::delete"
+ ],
+ "deletebatchset-null-constraints-null-batchsize-100": [
+ "CodeIgniter\\Database\\BaseBuilder::deleteBatch"
+ ],
+ "incrementcolumn-value-1": [
+ "CodeIgniter\\Database\\BaseBuilder::increment"
+ ],
+ "decrementcolumn-value-1": [
+ "CodeIgniter\\Database\\BaseBuilder::decrement"
+ ],
+ "truncate": [
+ "CodeIgniter\\Database\\BaseBuilder::truncate"
+ ],
+ "emptytable": [
+ "CodeIgniter\\Database\\BaseBuilder::emptyTable"
+ ],
+ "getcompiledselectreset-true": [
+ "CodeIgniter\\Database\\BaseBuilder::getCompiledSelect"
+ ],
+ "getcompiledinsertreset-true": [
+ "CodeIgniter\\Database\\BaseBuilder::getCompiledInsert"
+ ],
+ "getcompiledupdatereset-true": [
+ "CodeIgniter\\Database\\BaseBuilder::getCompiledUpdate"
+ ],
+ "getcompileddeletereset-true": [
+ "CodeIgniter\\Database\\BaseBuilder::getCompiledDelete"
+ ]
+ },
+ "database/results.md": {
+ "codeigniterdatabase": [
+ "namespace-CodeIgniter\\Database"
+ ],
+ "baseresult": [
+ "CodeIgniter\\Database\\BaseResult"
+ ],
+ "getresulttype-object": [
+ "CodeIgniter\\Database\\BaseResult::getResult"
+ ],
+ "getresultarray_1": [
+ "CodeIgniter\\Database\\BaseResult::getResultArray"
+ ],
+ "getresultobject": [
+ "CodeIgniter\\Database\\BaseResult::getResultObject"
+ ],
+ "getcustomresultobjectclassname": [
+ "CodeIgniter\\Database\\BaseResult::getCustomResultObject"
+ ],
+ "getrown-0-type-object": [
+ "CodeIgniter\\Database\\BaseResult::getRow"
+ ],
+ "getunbufferedrowtype-object": [
+ "CodeIgniter\\Database\\BaseResult::getUnbufferedRow"
+ ],
+ "getrowarrayn-0": [
+ "CodeIgniter\\Database\\BaseResult::getRowArray"
+ ],
+ "getrowobjectn-0": [
+ "CodeIgniter\\Database\\BaseResult::getRowObject"
+ ],
+ "getcustomrowobjectn-classname": [
+ "CodeIgniter\\Database\\BaseResult::getCustomRowObject"
+ ],
+ "dataseekn-0": [
+ "CodeIgniter\\Database\\BaseResult::dataSeek"
+ ],
+ "setrowkey-value-null": [
+ "CodeIgniter\\Database\\BaseResult::setRow"
+ ],
+ "getnextrowtype-object": [
+ "CodeIgniter\\Database\\BaseResult::getNextRow"
+ ],
+ "getpreviousrowtype-object": [
+ "CodeIgniter\\Database\\BaseResult::getPreviousRow"
+ ],
+ "getfirstrowtype-object": [
+ "CodeIgniter\\Database\\BaseResult::getFirstRow"
+ ],
+ "getlastrowtype-object": [
+ "CodeIgniter\\Database\\BaseResult::getLastRow"
+ ],
+ "getfieldcount_1": [
+ "CodeIgniter\\Database\\BaseResult::getFieldCount"
+ ],
+ "getfieldnames_1": [
+ "CodeIgniter\\Database\\BaseResult::getFieldNames"
+ ],
+ "getfielddata": [
+ "CodeIgniter\\Database\\BaseResult::getFieldData"
+ ],
+ "getnumrows_1": [
+ "CodeIgniter\\Database\\BaseResult::getNumRows"
+ ],
+ "freeresult_1": [
+ "CodeIgniter\\Database\\BaseResult::freeResult"
+ ]
+ },
+ "database/transactions.md": {
+ "codeigniters-approach-to-transactions": [
+ "codeigniter-s-approach-to-transactions"
+ ],
+ "resetting-transaction-status": [
+ "transactions-resetting-transaction-status"
+ ],
+ "managing-errors": [
+ "transactions-managing-errors"
+ ],
+ "throwing-exceptions": [
+ "transactions-throwing-exceptions"
+ ],
+ "running-transactions-manually": [
+ "transactions-manual-transactions"
+ ]
+ },
+ "dbmgmt/db_commands.md": {
+ "dbtable-show": [
+ "db-table-show"
+ ],
+ "specify-the-database-group": [
+ "db-command-specify-the-dbgroup"
+ ],
+ "dbtable-dbgroup": [
+ "db-table-dbgroup"
+ ],
+ "dbtable": [
+ "db-table"
+ ],
+ "dbtable-metadata": [
+ "db-table-metadata"
+ ]
+ },
+ "dbmgmt/forge.md": {
+ "forge-createdatabasedb_name": [
+ "forge-createdatabase-db-name"
+ ],
+ "forge-dropdatabasedb_name": [
+ "forge-dropdatabase-db-name"
+ ],
+ "raw-sql-strings-as-default-values": [
+ "forge-addfield-default-value-rawsql"
+ ],
+ "forge-dropcolumn": [
+ "db-forge-dropcolumn"
+ ],
+ "forge-modifycolumn": [
+ "db-forge-modifycolumn"
+ ],
+ "adding-keys-to-a-table": [
+ "db-forge-adding-keys-to-a-table"
+ ],
+ "codeigniterdatabase": [
+ "namespace-CodeIgniter\\Database"
+ ],
+ "forge": [
+ "CodeIgniter\\Database\\Forge"
+ ],
+ "addcolumntable-field": [
+ "CodeIgniter\\Database\\Forge::addColumn"
+ ],
+ "addfieldfield": [
+ "CodeIgniter\\Database\\Forge::addField"
+ ],
+ "addforeignkeyfieldname-tablename-tablefield-onupdate-ondelete-fkname": [
+ "CodeIgniter\\Database\\Forge::addForeignKey"
+ ],
+ "addkeykey-primary-false-unique-false-keyname": [
+ "CodeIgniter\\Database\\Forge::addKey"
+ ],
+ "addprimarykeykey-keyname": [
+ "CodeIgniter\\Database\\Forge::addPrimaryKey"
+ ],
+ "adduniquekeykey-keyname": [
+ "CodeIgniter\\Database\\Forge::addUniqueKey"
+ ],
+ "createdatabasedbname-ifnotexists-false": [
+ "CodeIgniter\\Database\\Forge::createDatabase"
+ ],
+ "createtabletable-ifnotexists-false-array-attributes": [
+ "CodeIgniter\\Database\\Forge::createTable"
+ ],
+ "dropcolumntable-columnnames": [
+ "CodeIgniter\\Database\\Forge::dropColumn"
+ ],
+ "dropdatabasedbname": [
+ "CodeIgniter\\Database\\Forge::dropDatabase"
+ ],
+ "dropkeytable-keyname-prefixkeyname-true": [
+ "CodeIgniter\\Database\\Forge::dropKey"
+ ],
+ "dropprimarykeytable-keyname": [
+ "CodeIgniter\\Database\\Forge::dropPrimaryKey"
+ ],
+ "droptabletablename-ifexists-false-cascade-false": [
+ "CodeIgniter\\Database\\Forge::dropTable"
+ ],
+ "processindexestable": [
+ "CodeIgniter\\Database\\Forge::processIndexes"
+ ],
+ "modifycolumntable-field": [
+ "CodeIgniter\\Database\\Forge::modifyColumn"
+ ],
+ "renametabletablename-newtablename": [
+ "CodeIgniter\\Database\\Forge::renameTable"
+ ]
+ },
+ "dbmgmt/migration.md": {
+ "migraterollback": [
+ "migrate-rollback"
+ ],
+ "migraterefresh": [
+ "migrate-refresh"
+ ],
+ "migratestatus": [
+ "migrate-status"
+ ],
+ "makemigration": [
+ "make-migration"
+ ],
+ "codeigniterdatabase": [
+ "namespace-CodeIgniter\\Database"
+ ],
+ "migrationrunner": [
+ "CodeIgniter\\Database\\MigrationRunner"
+ ],
+ "findmigrations": [
+ "CodeIgniter\\Database\\MigrationRunner::findMigrations"
+ ],
+ "latestgroup": [
+ "CodeIgniter\\Database\\MigrationRunner::latest"
+ ],
+ "regresstargetbatch-group": [
+ "CodeIgniter\\Database\\MigrationRunner::regress"
+ ],
+ "forcepath-namespace-group": [
+ "CodeIgniter\\Database\\MigrationRunner::force"
+ ],
+ "setnamespacenamespace": [
+ "CodeIgniter\\Database\\MigrationRunner::setNamespace"
+ ],
+ "setgroupgroup": [
+ "CodeIgniter\\Database\\MigrationRunner::setGroup"
+ ]
+ },
+ "extending/composer_packages.md": {
+ "creating-composerjson": [
+ "creating-composer-json"
+ ],
+ "overriding-settings-in-appconfig": [
+ "overriding-settings-in-app-config"
+ ]
+ },
+ "extending/events.md": {
+ "for-cli-apps": [
+ "event-points-for-cli-apps"
+ ]
+ },
+ "general/ajax.md": {
+ "vuejs": [
+ "vue-js"
+ ]
+ },
+ "general/caching.md": {
+ "setting-cachestatuscodes": [
+ "web-page-caching-cache-status-codes"
+ ]
+ },
+ "general/common_functions.md": {
+ "cachekey": [
+ "cache"
+ ],
+ "configstring-name-bool-getshared-true": [
+ "config"
+ ],
+ "cookiestring-name-string-value-array-options": [
+ "cookie"
+ ],
+ "cookiesarray-cookies-bool-getglobal-true": [
+ "cookies"
+ ],
+ "envkey-default-null": [
+ "env"
+ ],
+ "escdata-context-html-encoding": [
+ "esc"
+ ],
+ "helperfilename": [
+ "helper"
+ ],
+ "langline-args-locale": [
+ "lang"
+ ],
+ "modelname-getshared-true-conn-null": [
+ "model"
+ ],
+ "oldkey-default-null-escape-html": [
+ "old"
+ ],
+ "sessionkey": [
+ "session"
+ ],
+ "timername": [
+ "timer"
+ ],
+ "viewname-data-options": [
+ "view"
+ ],
+ "view_celllibrary-params-null-ttl-0-cachename-null": [
+ "view_cell"
+ ],
+ "force_httpsduration-31536000-request-null-response-null": [
+ "force_https"
+ ],
+ "function_usablefunctionname": [
+ "function_usable"
+ ],
+ "is_really_writablefile": [
+ "is_really_writable"
+ ],
+ "is_windowsmock-null": [
+ "is_windows"
+ ],
+ "log_messagelevel-message-context": [
+ "log_message"
+ ],
+ "redirectstring-route": [
+ "redirect"
+ ],
+ "remove_invisible_charactersstr-urlencoded-true": [
+ "remove_invisible_characters"
+ ],
+ "route_tomethod-params": [
+ "route_to"
+ ],
+ "servicename-params": [
+ "service"
+ ],
+ "single_servicename-params": [
+ "single_service"
+ ],
+ "slash_item-item": [
+ "slash_item"
+ ],
+ "stringify_attributesattributes-js": [
+ "stringify_attributes"
+ ],
+ "apppath": [
+ "APPPATH"
+ ],
+ "rootpath": [
+ "ROOTPATH"
+ ],
+ "systempath": [
+ "SYSTEMPATH"
+ ],
+ "fcpath": [
+ "FCPATH"
+ ],
+ "writepath": [
+ "WRITEPATH"
+ ],
+ "second": [
+ "SECOND"
+ ],
+ "minute": [
+ "MINUTE"
+ ],
+ "hour": [
+ "HOUR"
+ ],
+ "day": [
+ "DAY"
+ ],
+ "week": [
+ "WEEK"
+ ],
+ "month": [
+ "MONTH"
+ ],
+ "year": [
+ "YEAR"
+ ],
+ "decade": [
+ "DECADE"
+ ]
+ },
+ "general/configuration.md": {
+ "config": [
+ "configuration-config"
+ ],
+ "namespace-separator": [
+ "env-var-namespace-separator"
+ ],
+ "environment-variables-as-replacements-for-data": [
+ "env-var-replacements-for-data"
+ ],
+ "configcheck": [
+ "spark-config-check",
+ "config-check"
+ ]
+ },
+ "general/environments.md": {
+ "the-environment-constant": [
+ "environment-constant"
+ ],
+ "apache": [
+ "environment-apache"
+ ],
+ "nginx": [
+ "environment-nginx"
+ ]
+ },
+ "general/errors.md": {
+ "specify-http-status-code-in-your-exception": [
+ "error-specify-http-status-code"
+ ],
+ "specify-exit-code-in-your-exception": [
+ "error-specify-exit-code"
+ ]
+ },
+ "general/helpers.md": {
+ "loading-from-specified-namespace": [
+ "helpers-loading-from-specified-namespace"
+ ]
+ },
+ "general/logging.md": {
+ "configuration": [
+ "logging-configuration"
+ ]
+ },
+ "general/managing_apps.md": {
+ "renaming-or-relocating-the-application-directory": [
+ "renaming-app-directory"
+ ],
+ "running-multiple-applications-with-one-codeigniter-installation": [
+ "running-multiple-app"
+ ]
+ },
+ "general/modules.md": {
+ "enabledisable-discovery": [
+ "enable-disable-discovery"
+ ],
+ "specify-composer-packages": [
+ "modules-specify-composer-packages"
+ ],
+ "filters": [
+ "modules-filters"
+ ]
+ },
+ "general/urls.md": {
+ "url-structure": [
+ "urls-url-structure"
+ ],
+ "uri-security": [
+ "urls-uri-security"
+ ],
+ "removing-the-indexphp-file": [
+ "urls-remove-index-php",
+ "removing-the-index-php-file"
+ ],
+ "apache-web-server": [
+ "urls-remove-index-php-apache"
+ ],
+ "nginx": [
+ "urls-remove-index-php-nginx"
+ ]
+ },
+ "guides/api/controller.md": {
+ "building-a-restful-controller": [
+ "ci47-rest-part4"
+ ]
+ },
+ "guides/api/database-setup.md": {
+ "creating-the-database-and-model": [
+ "ci47-rest-part3"
+ ]
+ },
+ "guides/api/first-endpoint.md": {
+ "auto-routing-your-first-endpoint": [
+ "ci47-rest-part2"
+ ]
+ },
+ "guides/api/index.md": {
+ "getting-started-with-rest-apis": [
+ "ci47-rest-part1"
+ ],
+ "whats-next": [
+ "what-s-next"
+ ]
+ },
+ "guides/first-app/create_news_items.md": {
+ "create-newscreate-view-file": [
+ "create-news-create-view-file"
+ ],
+ "add-newsnew-to-display-the-form": [
+ "add-news-new-to-display-the-form"
+ ],
+ "add-newscreate-to-create-a-news-item": [
+ "add-news-create-to-create-a-news-item"
+ ]
+ },
+ "guides/first-app/news_section.md": {
+ "add-newsmodelgetnews-method": [
+ "add-newsmodel-getnews-method"
+ ],
+ "complete-newsindex-method": [
+ "complete-news-index-method"
+ ],
+ "create-newsindex-view-file": [
+ "create-news-index-view-file"
+ ],
+ "complete-newsshow-method": [
+ "complete-news-show-method"
+ ],
+ "create-newsview-view-file": [
+ "create-news-view-view-file"
+ ]
+ },
+ "guides/first-app/static_pages.md": {
+ "lets-make-our-first-controller": [
+ "let-s-make-our-first-controller"
+ ],
+ "create-homephp-and-aboutphp": [
+ "create-home-php-and-about-php"
+ ],
+ "complete-pagesview-method": [
+ "complete-pages-view-method"
+ ]
+ },
+ "helpers/array_helper.md": {
+ "dot_array_searchstring-search-array-values": [
+ "dot_array_search"
+ ],
+ "array_deep_searchkey-array-array": [
+ "array_deep_search"
+ ],
+ "array_sort_by_multiple_keysarray-array-array-sortcolumns": [
+ "array_sort_by_multiple_keys"
+ ],
+ "array_flatten_with_dotsiterable-array-string-id-array": [
+ "array_flatten_with_dots"
+ ],
+ "array_group_byarray-array-array-indexes-bool-includeempty-false-array": [
+ "array_group_by"
+ ]
+ },
+ "helpers/cookie_helper.md": {
+ "set_cookiename-value-expire-0-domain-path-prefix-secure-false-httponly-false-samesite": [
+ "set_cookie"
+ ],
+ "get_cookieindex-xssclean-false-prefix": [
+ "get_cookie"
+ ],
+ "delete_cookiename-domain-path-prefix": [
+ "delete_cookie"
+ ],
+ "has_cookiestring-name-string-value-null-string-prefix": [
+ "has_cookie"
+ ]
+ },
+ "helpers/date_helper.md": {
+ "nowtimezone-null": [
+ "now"
+ ],
+ "timezone_selectclass-default-what-datetimezoneall-country-null": [
+ "timezone_select"
+ ]
+ },
+ "helpers/filesystem_helper.md": {
+ "directory_mapsourcedir-directorydepth-0-hidden-false": [
+ "directory_map"
+ ],
+ "directory_mirrororiginal-target-overwrite-true": [
+ "directory_mirror"
+ ],
+ "write_filepath-data-mode-wb": [
+ "write_file"
+ ],
+ "delete_filespath-deldir-false-htdocs-false-hidden-false": [
+ "delete_files"
+ ],
+ "get_filenamessourcedir-includepath-false-hidden-false-includedir-true": [
+ "get_filenames"
+ ],
+ "get_dir_file_infosourcedir-toplevelonly-true": [
+ "get_dir_file_info"
+ ],
+ "get_file_infofile-returnedvalues-name-server_path-size-date": [
+ "get_file_info"
+ ],
+ "symbolic_permissionsperms": [
+ "symbolic_permissions"
+ ],
+ "octal_permissionsperms": [
+ "octal_permissions"
+ ],
+ "same_filefile1-file2": [
+ "same_file"
+ ],
+ "set_realpathpath-checkexistence-false": [
+ "set_realpath"
+ ]
+ },
+ "helpers/form_helper.md": {
+ "form_openaction-attributes-hidden": [
+ "form_open"
+ ],
+ "form_open_multipartaction-attributes-hidden": [
+ "form_open_multipart"
+ ],
+ "form_hiddenname-value": [
+ "form_hidden"
+ ],
+ "form_inputdata-value-extra-type-text": [
+ "form_input"
+ ],
+ "form_passworddata-value-extra": [
+ "form_password"
+ ],
+ "form_uploaddata-value-extra": [
+ "form_upload"
+ ],
+ "form_textareadata-value-extra": [
+ "form_textarea"
+ ],
+ "form_dropdownname-options-selected-extra": [
+ "form_dropdown"
+ ],
+ "form_multiselectname-options-selected-extra": [
+ "form_multiselect"
+ ],
+ "form_fieldsetlegend_text-attributes": [
+ "form_fieldset"
+ ],
+ "form_fieldset_closeextra": [
+ "form_fieldset_close"
+ ],
+ "form_checkboxdata-value-checked-false-extra": [
+ "form_checkbox"
+ ],
+ "form_radiodata-value-checked-false-extra": [
+ "form_radio"
+ ],
+ "form_labellabel_text-id-attributes": [
+ "form_label"
+ ],
+ "form_submitdata-value-extra": [
+ "form_submit"
+ ],
+ "form_resetdata-value-extra": [
+ "form_reset"
+ ],
+ "form_buttondata-content-extra": [
+ "form_button"
+ ],
+ "form_closeextra": [
+ "form_close"
+ ],
+ "set_valuefield-default-html_escape-true": [
+ "set_value"
+ ],
+ "set_selectfield-value-default-false": [
+ "set_select"
+ ],
+ "set_checkboxfield-value-default-false": [
+ "set_checkbox"
+ ],
+ "set_radiofield-value-default-false": [
+ "set_radio"
+ ],
+ "validation_list_errorstemplate-list": [
+ "validation_list_errors"
+ ],
+ "validation_show_errorfield-template-single": [
+ "validation_show_error"
+ ]
+ },
+ "helpers/html_helper.md": {
+ "imgsrc-indexpage-false-attributes": [
+ "img"
+ ],
+ "img_datapath-mime-null": [
+ "img_data"
+ ],
+ "link_taghref-rel-stylesheet-type-textcss-title-media-indexpage-false-hreflang": [
+ "link_tag"
+ ],
+ "script_tagsrc-indexpage-false": [
+ "script_tag"
+ ],
+ "ullist-attributes": [
+ "ul"
+ ],
+ "ollist-attributes": [
+ "ol"
+ ],
+ "videosrc-unsupportedmessage-attributes-tracks-indexpage-false": [
+ "video"
+ ],
+ "audiosrc-unsupportedmessage-attributes-tracks-indexpage-false": [
+ "audio"
+ ],
+ "sourcesrc-type-unknown-attributes-indexpage-false": [
+ "source"
+ ],
+ "embedsrc-type-unknown-attributes-indexpage-false": [
+ "embed"
+ ],
+ "objectdata-type-unknown-attributes-params-indexpage-false": [
+ "object"
+ ],
+ "paramname-value-type-ref-attributes": [
+ "param"
+ ],
+ "tracksrc-kind-srclanguage-label": [
+ "track"
+ ],
+ "doctypetype-html5": [
+ "doctype"
+ ]
+ },
+ "helpers/inflector_helper.md": {
+ "singularstring": [
+ "singular"
+ ],
+ "pluralstring": [
+ "plural"
+ ],
+ "countedcount-string": [
+ "counted"
+ ],
+ "camelizestring": [
+ "camelize"
+ ],
+ "pascalizestring": [
+ "pascalize"
+ ],
+ "underscorestring": [
+ "underscore"
+ ],
+ "decamelizestring": [
+ "decamelize"
+ ],
+ "humanizestring-separator-_": [
+ "humanize"
+ ],
+ "is_pluralizableword": [
+ "is_pluralizable"
+ ],
+ "dasherizestring": [
+ "dasherize"
+ ],
+ "ordinalinteger": [
+ "ordinal"
+ ],
+ "ordinalizeinteger": [
+ "ordinalize"
+ ]
+ },
+ "helpers/number_helper.md": {
+ "number_to_sizenum-precision-1-locale-null": [
+ "number_to_size"
+ ],
+ "number_to_amountnum-precision-0-locale-null": [
+ "number_to_amount"
+ ],
+ "number_to_currencynum-currency-locale-null-fraction-0": [
+ "number_to_currency"
+ ],
+ "number_to_romannum": [
+ "number_to_roman"
+ ]
+ },
+ "helpers/security_helper.md": {
+ "sanitize_filenamefilename-relativepath-false": [
+ "sanitize_filename"
+ ],
+ "strip_image_tagsstr": [
+ "strip_image_tags"
+ ],
+ "encode_php_tagsstr": [
+ "encode_php_tags"
+ ]
+ },
+ "helpers/test_helper.md": {
+ "fakemodel-array-overrides-null-persist-true": [
+ "fake"
+ ]
+ },
+ "helpers/text_helper.md": {
+ "random_stringtype-alnum-len-8": [
+ "random_string"
+ ],
+ "increment_stringstr-separator-_-first-1": [
+ "increment_string"
+ ],
+ "alternatorargs": [
+ "alternator"
+ ],
+ "reduce_double_slashesstr": [
+ "reduce_double_slashes"
+ ],
+ "strip_slashesdata": [
+ "strip_slashes"
+ ],
+ "reduce_multiplesstr-character-trim-false": [
+ "reduce_multiples"
+ ],
+ "quotes_to_entitiesstr": [
+ "quotes_to_entities"
+ ],
+ "strip_quotesstr": [
+ "strip_quotes"
+ ],
+ "word_limiterstr-limit-100-endchar-8230": [
+ "word_limiter"
+ ],
+ "character_limiterstring-limit-500-endchar-8230": [
+ "character_limiter"
+ ],
+ "ascii_to_entitiesstr": [
+ "ascii_to_entities"
+ ],
+ "entities_to_asciistr-all-true": [
+ "entities_to_ascii"
+ ],
+ "convert_accented_charactersstr": [
+ "convert_accented_characters"
+ ],
+ "word_censorstr-censored-replacement": [
+ "word_censor"
+ ],
+ "highlight_codestr": [
+ "highlight_code"
+ ],
+ "highlight_phrasestr-phrase-tag_open-mark-tag_close-mark": [
+ "highlight_phrase"
+ ],
+ "word_wrapstr-charlim-76": [
+ "word_wrap"
+ ],
+ "ellipsizestr-max_length-position-1-ellipsis-hellip": [
+ "ellipsize"
+ ],
+ "excerpttext-phrase-false-radius-100-ellipsis": [
+ "excerpt"
+ ]
+ },
+ "helpers/url_helper.md": {
+ "site_urluri-protocol-null-altconfig-null": [
+ "site_url"
+ ],
+ "base_urluri-protocol-null": [
+ "base_url"
+ ],
+ "current_urlreturnobject-false-request-null": [
+ "current_url"
+ ],
+ "previous_urlreturnobject-false": [
+ "previous_url"
+ ],
+ "index_pagealtconfig-null": [
+ "index_page"
+ ],
+ "anchoruri-title-attributes-altconfig-null": [
+ "anchor"
+ ],
+ "anchor_popupuri-title-attributes-false-altconfig-null": [
+ "anchor_popup"
+ ],
+ "mailtoemail-title-attributes": [
+ "mailto"
+ ],
+ "safe_mailtoemail-title-attributes": [
+ "safe_mailto"
+ ],
+ "auto_linkstr-type-both-popup-false": [
+ "auto_link"
+ ],
+ "url_titlestr-separator-lowercase-false": [
+ "url_title"
+ ],
+ "mb_url_titlestr-separator-lowercase-false": [
+ "mb_url_title"
+ ],
+ "parse_subdomainhost-null": [
+ "parse_subdomain"
+ ],
+ "prep_urlstr-secure-false": [
+ "prep_url"
+ ],
+ "url_tocontroller-args": [
+ "url_to"
+ ],
+ "url_ispath": [
+ "url_is"
+ ]
+ },
+ "helpers/xml_helper.md": {
+ "xml_convertstr-protect_all-false": [
+ "xml_convert"
+ ]
+ },
+ "incoming/auto_routing_improved.md": {
+ "enable-auto-routing-improved": [
+ "enabled-auto-routing-improved"
+ ],
+ "lets-try-it-hello-world": [
+ "let-s-try-it-hello-world"
+ ],
+ "default-method-fallback": [
+ "auto-routing-improved-default-method-fallback"
+ ],
+ "examples-of-controllermethods-and-uris": [
+ "examples-of-controller-methods-and-uris"
+ ],
+ "configuration-options": [
+ "routing-auto-routing-improved-configuration-options"
+ ],
+ "default-method_1": [
+ "routing-auto-routing-improved-default-method"
+ ],
+ "module-routing": [
+ "auto-routing-improved-module-routing"
+ ]
+ },
+ "incoming/controller_attributes.md": {
+ "controller-attributes": [
+ "incoming-controller-attributes"
+ ]
+ },
+ "incoming/controllers.md": {
+ "constructor": [
+ "controller-constructor"
+ ],
+ "helpers": [
+ "controllers-helpers"
+ ],
+ "validating-data": [
+ "controllers-validating-data"
+ ],
+ "this-validatedata": [
+ "controller-validatedata"
+ ],
+ "this-validate": [
+ "controller-validate"
+ ],
+ "auto-routing-improved": [
+ "controller-auto-routing-improved"
+ ],
+ "auto-routing-legacy": [
+ "controller-auto-routing-legacy"
+ ],
+ "lets-try-it-hello-world-legacy": [
+ "let-s-try-it-hello-world-legacy"
+ ],
+ "thats-it": [
+ "that-s-it"
+ ]
+ },
+ "incoming/filters.md": {
+ "appconfigfiltersphp": [
+ "app-config-filters-php"
+ ],
+ "aliases": [
+ "filters-aliases"
+ ],
+ "required": [
+ "filters-required"
+ ],
+ "globals": [
+ "filters-globals"
+ ],
+ "filter-arguments": [
+ "filters-filters-filter-arguments"
+ ],
+ "filtercheck": [
+ "spark-filter-check",
+ "filter-check"
+ ]
+ },
+ "incoming/incomingrequest.md": {
+ "is": [
+ "incomingrequest-is"
+ ],
+ "getting-data": [
+ "incomingrequest-getting-data"
+ ],
+ "getting-json-data": [
+ "incomingrequest-getting-json-data"
+ ],
+ "retrieving-raw-data-put-patch-delete": [
+ "incomingrequest-retrieving-raw-data"
+ ],
+ "filtering-input-data": [
+ "incomingrequest-filtering-input-data"
+ ],
+ "codeigniterhttp": [
+ "namespace-CodeIgniter\\HTTP"
+ ],
+ "incomingrequest": [
+ "CodeIgniter\\HTTP\\IncomingRequest"
+ ],
+ "iscli": [
+ "CodeIgniter\\HTTP\\IncomingRequest::isCLI"
+ ],
+ "isajax": [
+ "CodeIgniter\\HTTP\\IncomingRequest::isAJAX"
+ ],
+ "issecure": [
+ "CodeIgniter\\HTTP\\IncomingRequest::isSecure"
+ ],
+ "getvarindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getVar"
+ ],
+ "getgetindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getGet"
+ ],
+ "getpostindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getPost"
+ ],
+ "getpostgetindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getPostGet"
+ ],
+ "getgetpostindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getGetPost"
+ ],
+ "getcookieindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getCookie"
+ ],
+ "getserverindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getServer"
+ ],
+ "getuseragent": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getUserAgent"
+ ],
+ "getpath": [
+ "CodeIgniter\\HTTP\\IncomingRequest::getPath"
+ ]
+ },
+ "incoming/message.md": {
+ "codeigniterhttp": [
+ "namespace-CodeIgniter\\HTTP"
+ ],
+ "message": [
+ "CodeIgniter\\HTTP\\Message"
+ ],
+ "getbody": [
+ "CodeIgniter\\HTTP\\Message::getBody"
+ ],
+ "setbodydata": [
+ "CodeIgniter\\HTTP\\Message::setBody"
+ ],
+ "appendbodydata": [
+ "CodeIgniter\\HTTP\\Message::appendBody"
+ ],
+ "populateheaders": [
+ "CodeIgniter\\HTTP\\Message::populateHeaders"
+ ],
+ "headers": [
+ "CodeIgniter\\HTTP\\Message::headers"
+ ],
+ "headername": [
+ "CodeIgniter\\HTTP\\Message::header"
+ ],
+ "hasheadername": [
+ "CodeIgniter\\HTTP\\Message::hasHeader"
+ ],
+ "getheaderlinename": [
+ "CodeIgniter\\HTTP\\Message::getHeaderLine"
+ ],
+ "setheadername-value": [
+ "CodeIgniter\\HTTP\\Message::setHeader"
+ ],
+ "removeheadername": [
+ "CodeIgniter\\HTTP\\Message::removeHeader"
+ ],
+ "appendheadername-value": [
+ "CodeIgniter\\HTTP\\Message::appendHeader"
+ ],
+ "prependheadername-value": [
+ "CodeIgniter\\HTTP\\Message::prependHeader"
+ ],
+ "addheadername-value": [
+ "CodeIgniter\\HTTP\\Message::addHeader"
+ ],
+ "getprotocolversion": [
+ "CodeIgniter\\HTTP\\Message::getProtocolVersion"
+ ],
+ "setprotocolversionversion": [
+ "CodeIgniter\\HTTP\\Message::setProtocolVersion"
+ ]
+ },
+ "incoming/request.md": {
+ "codeigniterhttp": [
+ "namespace-CodeIgniter\\HTTP"
+ ],
+ "request": [
+ "CodeIgniter\\HTTP\\Request"
+ ],
+ "getipaddress": [
+ "CodeIgniter\\HTTP\\Request::getIPAddress"
+ ],
+ "isvalidipip-which": [
+ "CodeIgniter\\HTTP\\Request::isValidIP"
+ ],
+ "getmethod": [
+ "CodeIgniter\\HTTP\\Request::getMethod"
+ ],
+ "setmethodmethod": [
+ "CodeIgniter\\HTTP\\Request::setMethod"
+ ],
+ "withmethodmethod": [
+ "CodeIgniter\\HTTP\\Request::withMethod"
+ ],
+ "getserverindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\Request::getServer"
+ ],
+ "getenvindex-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\Request::getEnv"
+ ],
+ "setglobalmethod-value": [
+ "CodeIgniter\\HTTP\\Request::setGlobal"
+ ],
+ "fetchglobalmethod-index-null-filter-null-flags-null": [
+ "CodeIgniter\\HTTP\\Request::fetchGlobal"
+ ]
+ },
+ "incoming/restful.md": {
+ "presentercontroller-comparison": [
+ "presenter-controller-comparison"
+ ]
+ },
+ "incoming/routing.md": {
+ "setting-routing-rules": [
+ "defined-route-routing"
+ ],
+ "http-verb-routes": [
+ "routing-http-verb-routes"
+ ],
+ "controllers-namespace": [
+ "controller-s-namespace"
+ ],
+ "the-behavior-of-any": [
+ "routing-placeholder-any"
+ ],
+ "named-routes": [
+ "using-named-routes"
+ ],
+ "nesting-groups": [
+ "routing-nesting-groups"
+ ],
+ "route-priority": [
+ "routing-priority"
+ ],
+ "default-namespace": [
+ "routing-default-namespace"
+ ],
+ "default-method": [
+ "routing-default-method"
+ ],
+ "404-override": [
+ "override"
+ ],
+ "configuration-options-legacy": [
+ "routing-auto-routing-legacy-configuration-options"
+ ],
+ "default-method-legacy": [
+ "routing-auto-routing-legacy-default-method"
+ ],
+ "spark-routes": [
+ "routing-spark-routes"
+ ],
+ "sort-by-handler": [
+ "routing-spark-routes-sort-by-handler"
+ ],
+ "specify-host": [
+ "routing-spark-routes-specify-host"
+ ],
+ "retrieving-the-current-controllermethod-names": [
+ "retrieving-the-current-controller-method-names"
+ ]
+ },
+ "installation/installing_composer.md": {
+ "upgrading": [
+ "app-starter-upgrading"
+ ],
+ "latest-dev": [
+ "switch-to-dev-version"
+ ],
+ "upgrading_1": [
+ "adding-codeigniter4-upgrading"
+ ]
+ },
+ "installation/installing_manual.md": {
+ "upgrading": [
+ "installing-manual-upgrading"
+ ]
+ },
+ "installation/running.md": {
+ "checking-php-ini-settings": [
+ "spark-phpini-check"
+ ],
+ "enabling-mod_rewrite": [
+ "enabling-mod-rewrite"
+ ],
+ "enabling-vhost_alias_module": [
+ "enabling-vhost-alias-module"
+ ],
+ "hosting-with-mod_userdir-shared-hosts": [
+ "hosting-with-mod-userdir-shared-hosts"
+ ],
+ "removing-the-indexphp": [
+ "removing-the-index-php"
+ ],
+ "defaultconf": [
+ "default-conf"
+ ]
+ },
+ "installation/troubleshooting.md": {
+ "i-have-to-include-indexphp-in-my-url": [
+ "i-have-to-include-index-php-in-my-url"
+ ],
+ "whats-with-an-unhelpful-whoops-page": [
+ "what-s-with-an-unhelpful-whoops-page"
+ ]
+ },
+ "installation/upgrade_404.md": {
+ "upgrading-from-40x-to-404": [
+ "upgrading-from-4-0-x-to-4-0-4"
+ ]
+ },
+ "installation/upgrade_405.md": {
+ "upgrading-from-404-to-405": [
+ "upgrading-from-4-0-4-to-4-0-5"
+ ],
+ "messagegetheaders": [
+ "message-getheader-s"
+ ],
+ "configservices": [
+ "config-services"
+ ]
+ },
+ "installation/upgrade_410.md": {
+ "upgrading-from-405-to-410-or-411": [
+ "upgrading-from-4-0-5-to-4-1-0-or-4-1-1"
+ ]
+ },
+ "installation/upgrade_412.md": {
+ "upgrading-from-411-to-412": [
+ "upgrading-from-4-1-1-to-4-1-2"
+ ],
+ "current_url-and-indexpage": [
+ "current-url-and-indexpage"
+ ],
+ "baseconnectionquery-return-values": [
+ "baseconnection-query-return-values"
+ ],
+ "connectioninterfaceiswritetype-declaration-added": [
+ "connectioninterface-iswritetype-declaration-added"
+ ]
+ },
+ "installation/upgrade_413.md": {
+ "upgrading-from-412-to-413": [
+ "upgrading-from-4-1-2-to-4-1-3"
+ ]
+ },
+ "installation/upgrade_414.md": {
+ "upgrading-from-413-to-414": [
+ "upgrading-from-4-1-3-to-4-1-4"
+ ]
+ },
+ "installation/upgrade_415.md": {
+ "upgrading-from-414-to-415": [
+ "upgrading-from-4-1-4-to-4-1-5"
+ ],
+ "session-databasehandlers-database-table-change": [
+ "session-databasehandler-s-database-table-change"
+ ],
+ "multiple-filters-for-a-route": [
+ "upgrade-415-multiple-filters-for-a-route"
+ ]
+ },
+ "installation/upgrade_416.md": {
+ "upgrading-from-415-to-416": [
+ "upgrading-from-4-1-5-to-4-1-6"
+ ]
+ },
+ "installation/upgrade_417.md": {
+ "upgrading-from-416-to-417": [
+ "upgrading-from-4-1-6-to-4-1-7"
+ ]
+ },
+ "installation/upgrade_418.md": {
+ "upgrading-from-417-to-418": [
+ "upgrading-from-4-1-7-to-4-1-8"
+ ]
+ },
+ "installation/upgrade_420.md": {
+ "upgrading-from-419-to-420": [
+ "upgrading-from-4-1-9-to-4-2-0"
+ ],
+ "indexphp-and-spark": [
+ "index-php-and-spark"
+ ],
+ "configconstantsphp": [
+ "config-constants-php"
+ ],
+ "composerjson": [
+ "composer-json"
+ ]
+ },
+ "installation/upgrade_421.md": {
+ "upgrading-from-420-to-421": [
+ "upgrading-from-4-2-0-to-4-2-1"
+ ],
+ "appconfigmimesphp": [
+ "app-config-mimes-php"
+ ],
+ "get_cookie": [
+ "upgrade-421-get-cookie",
+ "get-cookie"
+ ]
+ },
+ "installation/upgrade_4210.md": {
+ "upgrading-from-429-to-4210": [
+ "upgrading-from-4-2-9-to-4-2-10"
+ ]
+ },
+ "installation/upgrade_4211.md": {
+ "upgrading-from-4210-to-4211": [
+ "upgrading-from-4-2-10-to-4-2-11"
+ ],
+ "configappproxyips": [
+ "upgrade-4211-proxyips",
+ "config-app-proxyips"
+ ],
+ "session-handler-key-changes": [
+ "upgrade-4211-session-key"
+ ]
+ },
+ "installation/upgrade_4212.md": {
+ "upgrading-from-4211-to-4212": [
+ "upgrading-from-4-2-11-to-4-2-12"
+ ]
+ },
+ "installation/upgrade_422.md": {
+ "upgrading-from-421-to-422": [
+ "upgrading-from-4-2-1-to-4-2-2"
+ ]
+ },
+ "installation/upgrade_423.md": {
+ "upgrading-from-422-to-423": [
+ "upgrading-from-4-2-2-to-4-2-3"
+ ]
+ },
+ "installation/upgrade_425.md": {
+ "upgrading-from-423-to-425": [
+ "upgrading-from-4-2-3-to-4-2-5"
+ ]
+ },
+ "installation/upgrade_426.md": {
+ "upgrading-from-425-to-426": [
+ "upgrading-from-4-2-5-to-4-2-6"
+ ]
+ },
+ "installation/upgrade_427.md": {
+ "upgrading-from-426-to-427": [
+ "upgrading-from-4-2-6-to-4-2-7"
+ ],
+ "set_cookie": [
+ "set-cookie"
+ ]
+ },
+ "installation/upgrade_428.md": {
+ "upgrading-from-427-to-428": [
+ "upgrading-from-4-2-7-to-4-2-8"
+ ]
+ },
+ "installation/upgrade_430.md": {
+ "upgrading-from-4212-to-430": [
+ "upgrading-from-4-2-12-to-4-3-0"
+ ],
+ "appconfigkintphp": [
+ "app-config-kint-php"
+ ],
+ "appconfigexceptionsphp": [
+ "app-config-exceptions-php"
+ ],
+ "composerjson": [
+ "composer-json"
+ ],
+ "capturing-stderr-and-stdout-streams-in-tests": [
+ "upgrade-430-stream-filter"
+ ],
+ "honeypot-and-csp": [
+ "upgrade-430-honeypot-and-csp"
+ ],
+ "config": [
+ "upgrade-430-config"
+ ]
+ },
+ "installation/upgrade_431.md": {
+ "upgrading-from-430-to-431": [
+ "upgrading-from-4-3-0-to-4-3-1"
+ ],
+ "appconfigemailphp": [
+ "app-config-email-php"
+ ],
+ "appconfigexceptionsphp": [
+ "app-config-exceptions-php"
+ ]
+ },
+ "installation/upgrade_432.md": {
+ "upgrading-from-431-to-432": [
+ "upgrading-from-4-3-1-to-4-3-2"
+ ],
+ "base_url": [
+ "base-url"
+ ],
+ "uri_string": [
+ "upgrade-432-uri-string",
+ "uri-string"
+ ],
+ "composerjson": [
+ "composer-json"
+ ]
+ },
+ "installation/upgrade_433.md": {
+ "upgrading-from-432-to-433": [
+ "upgrading-from-4-3-2-to-4-3-3"
+ ]
+ },
+ "installation/upgrade_434.md": {
+ "upgrading-from-433-to-434": [
+ "upgrading-from-4-3-3-to-4-3-4"
+ ],
+ "forgemodifycolumn-and-null": [
+ "forge-modifycolumn-and-null"
+ ]
+ },
+ "installation/upgrade_435.md": {
+ "upgrading-from-434-to-435": [
+ "upgrading-from-4-3-4-to-4-3-5"
+ ],
+ "sessionstop": [
+ "session-stop"
+ ]
+ },
+ "installation/upgrade_436.md": {
+ "upgrading-from-435-to-436": [
+ "upgrading-from-4-3-5-to-4-3-6"
+ ]
+ },
+ "installation/upgrade_437.md": {
+ "upgrading-from-436-to-437": [
+ "upgrading-from-4-3-6-to-4-3-7"
+ ],
+ "feature-testing-request-body": [
+ "upgrade-437-feature-testing"
+ ],
+ "return-value-of-validationloadrulegroup": [
+ "return-value-of-validation-loadrulegroup"
+ ]
+ },
+ "installation/upgrade_438.md": {
+ "upgrading-from-437-to-438": [
+ "upgrading-from-4-3-7-to-4-3-8"
+ ]
+ },
+ "installation/upgrade_440.md": {
+ "upgrading-from-438-to-440": [
+ "upgrading-from-4-3-8-to-4-4-0"
+ ],
+ "urisetsegment-change": [
+ "upgrade-440-uri-setsegment",
+ "uri-setsegment-change"
+ ],
+ "routecollectionroutes": [
+ "routecollection-routes"
+ ],
+ "indexphp-and-spark": [
+ "index-php-and-spark"
+ ],
+ "appconfigappphp": [
+ "app-config-app-php"
+ ],
+ "appconfigroutingphp": [
+ "upgrade-440-config-routing",
+ "app-config-routing-php"
+ ],
+ "appconfigtoolbarphp": [
+ "app-config-toolbar-php"
+ ],
+ "appconfigeventsphp": [
+ "app-config-events-php"
+ ],
+ "appconfigcookiephp": [
+ "app-config-cookie-php"
+ ],
+ "appconfigsecurityphp": [
+ "app-config-security-php"
+ ],
+ "appconfigsessionphp": [
+ "app-config-session-php"
+ ]
+ },
+ "installation/upgrade_441.md": {
+ "upgrading-from-440-to-441": [
+ "upgrading-from-4-4-0-to-4-4-1"
+ ]
+ },
+ "installation/upgrade_442.md": {
+ "upgrading-from-441-to-442": [
+ "upgrading-from-4-4-1-to-4-4-2"
+ ]
+ },
+ "installation/upgrade_443.md": {
+ "upgrading-from-442-to-443": [
+ "upgrading-from-4-4-2-to-4-4-3"
+ ],
+ "error_exceptionphp": [
+ "error-exception-php"
+ ]
+ },
+ "installation/upgrade_444.md": {
+ "upgrading-from-443-to-444": [
+ "upgrading-from-4-4-3-to-4-4-4"
+ ],
+ "validation-with-dot-array-syntax": [
+ "upgrade-444-validation-with-dot-array-syntax"
+ ],
+ "the-use-of-the-ssl_key-option-in-curlrequest-was-removed": [
+ "the-use-of-the-ssl-key-option-in-curlrequest-was-removed"
+ ]
+ },
+ "installation/upgrade_445.md": {
+ "upgrading-from-444-to-445": [
+ "upgrading-from-4-4-4-to-4-4-5"
+ ]
+ },
+ "installation/upgrade_446.md": {
+ "upgrading-from-445-to-446": [
+ "upgrading-from-4-4-5-to-4-4-6"
+ ],
+ "timecreatefromtimestamp-timezone-change": [
+ "time-createfromtimestamp-timezone-change"
+ ]
+ },
+ "installation/upgrade_447.md": {
+ "upgrading-from-446-to-447": [
+ "upgrading-from-4-4-6-to-4-4-7"
+ ],
+ "paths-in-controller-filters": [
+ "upgrade-447-filter-paths"
+ ],
+ "timedifference-and-dst": [
+ "time-difference-and-dst"
+ ]
+ },
+ "installation/upgrade_448.md": {
+ "upgrading-from-447-to-448": [
+ "upgrading-from-4-4-7-to-4-4-8"
+ ]
+ },
+ "installation/upgrade_450.md": {
+ "upgrading-from-448-to-450": [
+ "upgrading-from-4-4-8-to-4-5-0"
+ ],
+ "indexphp-and-spark": [
+ "index-php-and-spark"
+ ],
+ "lowercase-http-method-name": [
+ "upgrade-450-lowercase-http-method-name"
+ ],
+ "requestgetmethod": [
+ "request-getmethod"
+ ],
+ "appconfigfiltersphp": [
+ "app-config-filters-php"
+ ],
+ "curlrequestrequest": [
+ "curlrequest-request"
+ ],
+ "nested-route-groups-and-options": [
+ "upgrade-450-nested-route-groups-and-options"
+ ],
+ "filter-execution-order": [
+ "upgrade-450-filter-execution-order"
+ ],
+ "apiresponsetrait-and-string-data": [
+ "upgrade-450-api-response-trait",
+ "api-responsetrait-and-string-data"
+ ],
+ "filelocatorfindqualifiednamefrompath": [
+ "filelocator-findqualifiednamefrompath"
+ ],
+ "basemodelgetidvalue": [
+ "basemodel-getidvalue"
+ ],
+ "404-override-status-code": [
+ "upgrade-450-404-override",
+ "override-status-code"
+ ],
+ "validationrun-signature": [
+ "validation-run-signature"
+ ],
+ "appconfigfiltersphp_1": [
+ "app-config-filters-php"
+ ]
+ },
+ "installation/upgrade_451.md": {
+ "upgrading-from-450-to-451": [
+ "upgrading-from-4-5-0-to-4-5-1"
+ ]
+ },
+ "installation/upgrade_452.md": {
+ "upgrading-from-451-to-452": [
+ "upgrading-from-4-5-1-to-4-5-2"
+ ]
+ },
+ "installation/upgrade_453.md": {
+ "upgrading-from-452-to-453": [
+ "upgrading-from-4-5-2-to-4-5-3"
+ ]
+ },
+ "installation/upgrade_454.md": {
+ "upgrading-from-453-to-454": [
+ "upgrading-from-4-5-3-to-4-5-4"
+ ]
+ },
+ "installation/upgrade_455.md": {
+ "upgrading-from-454-to-455": [
+ "upgrading-from-4-5-4-to-4-5-5"
+ ]
+ },
+ "installation/upgrade_456.md": {
+ "upgrading-from-455-to-456": [
+ "upgrading-from-4-5-5-to-4-5-6"
+ ]
+ },
+ "installation/upgrade_457.md": {
+ "upgrading-from-456-to-457": [
+ "upgrading-from-4-5-6-to-4-5-7"
+ ]
+ },
+ "installation/upgrade_458.md": {
+ "upgrading-from-457-to-458": [
+ "upgrading-from-4-5-7-to-4-5-8"
+ ]
+ },
+ "installation/upgrade_460.md": {
+ "upgrading-from-458-to-460": [
+ "upgrading-from-4-5-8-to-4-6-0"
+ ],
+ "timecreatefromtimestamp-timezone-change": [
+ "upgrade-460-time-create-from-timestamp",
+ "time-createfromtimestamp-timezone-change"
+ ],
+ "time-keeps-microseconds": [
+ "upgrade-460-time-keeps-microseconds"
+ ],
+ "timesettimestamp-behavior-fix": [
+ "upgrade-460-time-set-timestamp",
+ "time-settimestamp-behavior-fix"
+ ],
+ "registrars-with-dirty-hack": [
+ "upgrade-460-registrars-with-dirty-hack"
+ ],
+ "session-id-sid-change": [
+ "upgrade-460-sid-change"
+ ],
+ "filters-changes": [
+ "upgrade-460-filters-changes"
+ ]
+ },
+ "installation/upgrade_461.md": {
+ "upgrading-from-460-to-461": [
+ "upgrading-from-4-6-0-to-4-6-1"
+ ]
+ },
+ "installation/upgrade_462.md": {
+ "upgrading-from-461-to-462": [
+ "upgrading-from-4-6-1-to-4-6-2"
+ ]
+ },
+ "installation/upgrade_463.md": {
+ "upgrading-from-462-to-463": [
+ "upgrading-from-4-6-2-to-4-6-3"
+ ]
+ },
+ "installation/upgrade_464.md": {
+ "upgrading-from-463-to-464": [
+ "upgrading-from-4-6-3-to-4-6-4"
+ ]
+ },
+ "installation/upgrade_465.md": {
+ "upgrading-from-464-to-465": [
+ "upgrading-from-4-6-4-to-4-6-5"
+ ]
+ },
+ "installation/upgrade_470.md": {
+ "upgrading-from-46x-to-470": [
+ "upgrading-from-4-6-x-to-4-7-0"
+ ],
+ "php-82-required": [
+ "php-8-2-required"
+ ],
+ "validation-regex_match-placeholders": [
+ "validation-regex-match-placeholders"
+ ]
+ },
+ "installation/upgrade_471.md": {
+ "upgrading-from-470-to-471": [
+ "upgrading-from-4-7-0-to-4-7-1"
+ ]
+ },
+ "installation/upgrade_472.md": {
+ "upgrading-from-471-to-472": [
+ "upgrading-from-4-7-1-to-4-7-2"
+ ]
+ },
+ "installation/upgrade_473.md": {
+ "upgrading-from-472-to-473": [
+ "upgrading-from-4-7-2-to-4-7-3"
+ ]
+ },
+ "installation/upgrade_474.md": {
+ "upgrading-from-473-to-474": [
+ "upgrading-from-4-7-3-to-4-7-4"
+ ]
+ },
+ "installation/upgrade_475.md": {
+ "upgrading-from-474-to-475": [
+ "upgrading-from-4-7-4-to-4-7-5"
+ ],
+ "view-parser-no-longer-parses-substituted-values": [
+ "upgrade-475-view-parser-values"
+ ]
+ },
+ "installation/upgrade_476.md": {
+ "upgrading-from-475-to-476": [
+ "upgrading-from-4-7-5-to-4-7-6"
+ ],
+ "file-cache-deletematching-no-longer-matches-subdirectories": [
+ "upgrade-476-cache-delete-matching"
+ ],
+ "model-field-protection-for-replace": [
+ "upgrade-476-model-replace"
+ ],
+ "named-binds-are-no-longer-replaced-inside-quoted-values": [
+ "upgrade-476-named-binds"
+ ]
+ },
+ "installation/upgrade_477.md": {
+ "upgrading-from-476-to-477": [
+ "upgrading-from-4-7-6-to-4-7-7"
+ ]
+ },
+ "installation/upgrade_4xx.md": {
+ "upgrading-from-3x-to-4x": [
+ "upgrading-from-3-x-to-4-x"
+ ]
+ },
+ "installation/upgrade_configuration.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_controllers.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_database.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_emails.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_encryption.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_file_upload.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_html_tables.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_images.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_localization.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_migrations.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_models.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_pagination.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_responses.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_routing.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_security.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_sessions.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_validations.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_view_parser.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/upgrade_views.md": {
+ "codeigniter-version-3x": [
+ "codeigniter-version-3-x"
+ ],
+ "codeigniter-version-4x": [
+ "codeigniter-version-4-x"
+ ]
+ },
+ "installation/worker_mode.md": {
+ "reset-event-listeners": [
+ "worker-mode-reset-event-listeners"
+ ]
+ },
+ "intro/requirements.md": {
+ "supported-databases": [
+ "requirements-supported-databases"
+ ]
+ },
+ "libraries/caching.md": {
+ "configuring-the-cache": [
+ "libraries-caching-configuring-the-cache"
+ ],
+ "cacheclear": [
+ "cache-clear"
+ ],
+ "cacheinfo": [
+ "cache-info"
+ ],
+ "codeignitercache": [
+ "namespace-CodeIgniter\\Cache"
+ ],
+ "cacheinterface": [
+ "CodeIgniter\\Cache\\CacheInterface"
+ ],
+ "issupported": [
+ "CodeIgniter\\Cache\\CacheInterface::isSupported"
+ ],
+ "getkey-mixed": [
+ "CodeIgniter\\Cache\\CacheInterface::get"
+ ],
+ "rememberstring-key-int-ttl-closure-callback": [
+ "CodeIgniter\\Cache\\CacheInterface::remember"
+ ],
+ "savestring-key-data-int-ttl-60": [
+ "CodeIgniter\\Cache\\CacheInterface::save"
+ ],
+ "deletekey-bool": [
+ "CodeIgniter\\Cache\\CacheInterface::delete"
+ ],
+ "deletematchingpattern-integer": [
+ "CodeIgniter\\Cache\\CacheInterface::deleteMatching"
+ ],
+ "incrementkey-offset-1-mixed": [
+ "CodeIgniter\\Cache\\CacheInterface::increment"
+ ],
+ "decrementkey-offset-1-mixed": [
+ "CodeIgniter\\Cache\\CacheInterface::decrement"
+ ],
+ "clean": [
+ "CodeIgniter\\Cache\\CacheInterface::clean"
+ ],
+ "getcacheinfo": [
+ "CodeIgniter\\Cache\\CacheInterface::getCacheInfo"
+ ],
+ "getmetadatastring-key": [
+ "CodeIgniter\\Cache\\CacheInterface::getMetadata"
+ ],
+ "validatekeystring-key-string-prefix": [
+ "CodeIgniter\\Cache\\CacheInterface::validateKey"
+ ]
+ },
+ "libraries/cookies.md": {
+ "accessing-cookies-attributes": [
+ "accessing-cookie-s-attributes"
+ ],
+ "validating-a-cookies-attributes": [
+ "validating-a-cookie-s-attributes"
+ ],
+ "addingremoving-cookies-in-store": [
+ "adding-removing-cookies-in-store"
+ ],
+ "codeignitercookie": [
+ "namespace-CodeIgniter\\Cookie"
+ ],
+ "cookie": [
+ "CodeIgniter\\Cookie\\Cookie"
+ ],
+ "setdefaultsconfig": [
+ "CodeIgniter\\Cookie\\Cookie::setDefaults"
+ ],
+ "fromheaderstringstring-header-bool-raw-false": [
+ "CodeIgniter\\Cookie\\Cookie::fromHeaderString"
+ ],
+ "__constructstring-name-string-value-array-options": [
+ "CodeIgniter\\Cookie\\Cookie::__construct"
+ ],
+ "getid": [
+ "CodeIgniter\\Cookie\\Cookie::getId"
+ ],
+ "getprefix-string": [
+ "CodeIgniter\\Cookie\\Cookie::getPrefix"
+ ],
+ "getname-string": [
+ "CodeIgniter\\Cookie\\Cookie::getName"
+ ],
+ "getprefixedname-string": [
+ "CodeIgniter\\Cookie\\Cookie::getPrefixedName"
+ ],
+ "getvalue-string": [
+ "CodeIgniter\\Cookie\\Cookie::getValue"
+ ],
+ "getexpirestimestamp-int": [
+ "CodeIgniter\\Cookie\\Cookie::getExpiresTimestamp"
+ ],
+ "getexpiresstring-string": [
+ "CodeIgniter\\Cookie\\Cookie::getExpiresString"
+ ],
+ "isexpired-bool": [
+ "CodeIgniter\\Cookie\\Cookie::isExpired"
+ ],
+ "getmaxage-int": [
+ "CodeIgniter\\Cookie\\Cookie::getMaxAge"
+ ],
+ "getdomain-string": [
+ "CodeIgniter\\Cookie\\Cookie::getDomain"
+ ],
+ "getpath-string": [
+ "CodeIgniter\\Cookie\\Cookie::getPath"
+ ],
+ "issecure-bool": [
+ "CodeIgniter\\Cookie\\Cookie::isSecure"
+ ],
+ "ishttponly-bool": [
+ "CodeIgniter\\Cookie\\Cookie::isHTTPOnly"
+ ],
+ "getsamesite-string": [
+ "CodeIgniter\\Cookie\\Cookie::getSameSite"
+ ],
+ "israw-bool": [
+ "CodeIgniter\\Cookie\\Cookie::isRaw"
+ ],
+ "getoptions-array": [
+ "CodeIgniter\\Cookie\\Cookie::getOptions"
+ ],
+ "withrawbool-raw-true": [
+ "CodeIgniter\\Cookie\\Cookie::withRaw"
+ ],
+ "withprefixstring-prefix": [
+ "CodeIgniter\\Cookie\\Cookie::withPrefix"
+ ],
+ "withnamestring-name": [
+ "CodeIgniter\\Cookie\\Cookie::withName"
+ ],
+ "withvaluestring-value": [
+ "CodeIgniter\\Cookie\\Cookie::withValue"
+ ],
+ "withexpiresexpires": [
+ "CodeIgniter\\Cookie\\Cookie::withExpires"
+ ],
+ "withexpired": [
+ "CodeIgniter\\Cookie\\Cookie::withExpired"
+ ],
+ "withneverexpiring": [
+ "CodeIgniter\\Cookie\\Cookie::withNeverExpiring"
+ ],
+ "withdomainstring-domain": [
+ "CodeIgniter\\Cookie\\Cookie::withDomain"
+ ],
+ "withpathstring-path": [
+ "CodeIgniter\\Cookie\\Cookie::withPath"
+ ],
+ "withsecurebool-secure-true": [
+ "CodeIgniter\\Cookie\\Cookie::withSecure"
+ ],
+ "withhttponlybool-httponly-true": [
+ "CodeIgniter\\Cookie\\Cookie::withHTTPOnly"
+ ],
+ "withsamesitestring-samesite": [
+ "CodeIgniter\\Cookie\\Cookie::withSameSite"
+ ],
+ "toheaderstring": [
+ "CodeIgniter\\Cookie\\Cookie::toHeaderString"
+ ],
+ "toarray": [
+ "CodeIgniter\\Cookie\\Cookie::toArray"
+ ],
+ "cookiestore": [
+ "CodeIgniter\\Cookie\\CookieStore"
+ ],
+ "fromcookieheadersarray-headers-bool-raw-false": [
+ "CodeIgniter\\Cookie\\CookieStore::fromCookieHeaders"
+ ],
+ "__constructarray-cookies": [
+ "CodeIgniter\\Cookie\\CookieStore::__construct"
+ ],
+ "hasstring-name-string-prefix-string-value-null-bool": [
+ "CodeIgniter\\Cookie\\CookieStore::has"
+ ],
+ "getstring-name-string-prefix-cookie": [
+ "CodeIgniter\\Cookie\\CookieStore::get"
+ ],
+ "putcookie-cookie-cookiestore": [
+ "CodeIgniter\\Cookie\\CookieStore::put"
+ ],
+ "removestring-name-string-prefix-cookiestore": [
+ "CodeIgniter\\Cookie\\CookieStore::remove"
+ ],
+ "dispatch-void": [
+ "CodeIgniter\\Cookie\\CookieStore::dispatch"
+ ],
+ "display-array": [
+ "CodeIgniter\\Cookie\\CookieStore::display"
+ ],
+ "clear-void": [
+ "CodeIgniter\\Cookie\\CookieStore::clear"
+ ]
+ },
+ "libraries/cors.md": {
+ "set-in-configfilters": [
+ "set-in-config-filters"
+ ],
+ "codeigniterhttp": [
+ "namespace-CodeIgniter\\HTTP"
+ ],
+ "cors": [
+ "CodeIgniter\\HTTP\\Cors"
+ ],
+ "addresponseheadersrequestinterface-request-responseinterface-response-responseinterface": [
+ "CodeIgniter\\HTTP\\Cors::addResponseHeaders"
+ ],
+ "handlepreflightrequestrequestinterface-request-responseinterface-response-responseinterface": [
+ "CodeIgniter\\HTTP\\Cors::handlePreflightRequest"
+ ],
+ "ispreflightrequestincomingrequest-request-bool": [
+ "CodeIgniter\\HTTP\\Cors::isPreflightRequest"
+ ]
+ },
+ "libraries/curlrequest.md": {
+ "sharing-connection": [
+ "curlrequest-sharing-connection"
+ ],
+ "sharing-options": [
+ "curlrequest-sharing-options"
+ ],
+ "allow_redirects": [
+ "allow-redirects"
+ ],
+ "connect_timeout": [
+ "connect-timeout"
+ ],
+ "dns_cache_timeout": [
+ "dns-cache-timeout"
+ ],
+ "form_params": [
+ "form-params"
+ ],
+ "fresh_connect": [
+ "fresh-connect"
+ ],
+ "headers": [
+ "curlrequest-request-options-headers"
+ ],
+ "http_errors": [
+ "http-errors"
+ ],
+ "proxy": [
+ "curlrequest-request-options-proxy"
+ ],
+ "user_agent": [
+ "user-agent"
+ ],
+ "force_ip_resolve": [
+ "force-ip-resolve"
+ ],
+ "version": [
+ "curlrequest-version"
+ ]
+ },
+ "libraries/email.md": {
+ "ssl-versus-tls-for-smtp-protocol": [
+ "email-ssl-tls-for-smtp"
+ ],
+ "codeigniteremail": [
+ "namespace-CodeIgniter\\Email"
+ ],
+ "email": [
+ "CodeIgniter\\Email\\Email"
+ ],
+ "setfromfrom-name-returnpath-null": [
+ "CodeIgniter\\Email\\Email::setFrom"
+ ],
+ "setreplytoreplyto-name": [
+ "CodeIgniter\\Email\\Email::setReplyTo"
+ ],
+ "settoto": [
+ "CodeIgniter\\Email\\Email::setTo"
+ ],
+ "setcccc": [
+ "CodeIgniter\\Email\\Email::setCC"
+ ],
+ "setbccbcc-limit": [
+ "CodeIgniter\\Email\\Email::setBCC"
+ ],
+ "setsubjectsubject": [
+ "CodeIgniter\\Email\\Email::setSubject"
+ ],
+ "setmessagebody": [
+ "CodeIgniter\\Email\\Email::setMessage"
+ ],
+ "setaltmessagestr": [
+ "CodeIgniter\\Email\\Email::setAltMessage"
+ ],
+ "setheaderheader-value": [
+ "CodeIgniter\\Email\\Email::setHeader"
+ ],
+ "clearclearattachments-false": [
+ "CodeIgniter\\Email\\Email::clear"
+ ],
+ "sendautoclear-true": [
+ "CodeIgniter\\Email\\Email::send"
+ ],
+ "attachfilename-disposition-newname-null-mime": [
+ "CodeIgniter\\Email\\Email::attach"
+ ],
+ "setattachmentcidfilename": [
+ "CodeIgniter\\Email\\Email::setAttachmentCID"
+ ],
+ "printdebuggerinclude-headers-subject-body": [
+ "CodeIgniter\\Email\\Email::printDebugger"
+ ]
+ },
+ "libraries/encryption.md": {
+ "using-the-encryption-library": [
+ "usage"
+ ],
+ "configuring-the-library": [
+ "configuration"
+ ],
+ "configuration-to-maintain-compatibility-with-ci3": [
+ "encryption-compatible-with-ci3"
+ ],
+ "codeigniterencryption": [
+ "namespace-CodeIgniter\\Encryption"
+ ],
+ "encryption": [
+ "CodeIgniter\\Encryption\\Encryption"
+ ],
+ "createkeylength-32": [
+ "CodeIgniter\\Encryption\\Encryption::createKey"
+ ],
+ "initializeencryption-config-null": [
+ "CodeIgniter\\Encryption\\Encryption::initialize"
+ ],
+ "codeigniterencryptionencrypterinterface": [
+ "CodeIgniter\\Encryption\\CodeIgniter\\Encryption\\EncrypterInterface"
+ ],
+ "encryptdata-params-null": [
+ "CodeIgniter\\Encryption\\CodeIgniter\\Encryption\\EncrypterInterface::encrypt"
+ ],
+ "decryptdata-params-null": [
+ "CodeIgniter\\Encryption\\CodeIgniter\\Encryption\\EncrypterInterface::decrypt"
+ ]
+ },
+ "libraries/file_collections.md": {
+ "__constructstring-files": [
+ "construct-string-files"
+ ],
+ "setarray-files": [
+ "set-array-files"
+ ],
+ "addstringstring-paths-bool-recursive-true": [
+ "add-string-string-paths-bool-recursive-true"
+ ],
+ "addfilestring-file-addfilesarray-files": [
+ "addfile-string-file-addfiles-array-files"
+ ],
+ "removefilestring-file-removefilesarray-files": [
+ "removefile-string-file-removefiles-array-files"
+ ],
+ "adddirectorystring-directory-bool-recursive-false": [
+ "adddirectory-string-directory-bool-recursive-false"
+ ],
+ "removepatternstring-pattern-string-scope-null": [
+ "removepattern-string-pattern-string-scope-null"
+ ],
+ "retainpatternstring-pattern-string-scope-null": [
+ "retainpattern-string-pattern-string-scope-null"
+ ],
+ "retainmultiplepatternsarray-patterns-string-scope-null": [
+ "file-collections-retain-multiple-patterns",
+ "retainmultiplepatterns-array-patterns-string-scope-null"
+ ]
+ },
+ "libraries/files.md": {
+ "getsizebybinaryunit": [
+ "file-get-size-by-binary-unit"
+ ],
+ "getsizebymetricunit": [
+ "file-get-size-by-metric-unit"
+ ]
+ },
+ "libraries/publisher.md": {
+ "library-reference": [
+ "reference"
+ ],
+ "static-discoverstring-directory-publishers-string-namespace-publisher": [
+ "static-discover-string-directory-publishers-string-namespace-publisher"
+ ],
+ "geterrors-arraystring-throwable": [
+ "geterrors-array-string-throwable"
+ ],
+ "addpathstring-path-bool-recursive-true": [
+ "addpath-string-path-bool-recursive-true"
+ ],
+ "addpathsarray-paths-bool-recursive-true": [
+ "addpaths-array-paths-bool-recursive-true"
+ ],
+ "adduristring-uri": [
+ "adduri-string-uri"
+ ],
+ "addurisarray-uris": [
+ "adduris-array-uris"
+ ],
+ "copybool-replace-true-bool": [
+ "copy-bool-replace-true-bool"
+ ],
+ "mergebool-replace-true-bool": [
+ "merge-bool-replace-true-bool"
+ ],
+ "modifying-files": [
+ "publisher-modifying-files"
+ ],
+ "replacestring-file-array-replaces-bool": [
+ "replace-string-file-array-replaces-bool"
+ ],
+ "addlineafterstring-file-string-line-string-after-bool": [
+ "addlineafter-string-file-string-line-string-after-bool"
+ ],
+ "addlinebeforestring-file-string-line-string-before-bool": [
+ "addlinebefore-string-file-string-line-string-before-bool"
+ ]
+ },
+ "libraries/security.md": {
+ "cross-site-request-forgery-csrf": [
+ "cross-site-request-forgery"
+ ],
+ "redirection-on-failure": [
+ "csrf-redirection-on-failure"
+ ]
+ },
+ "libraries/sessions.md": {
+ "flashdata": [
+ "sessions-flashdata"
+ ],
+ "close": [
+ "session-close"
+ ],
+ "destroy": [
+ "session-destroy"
+ ],
+ "databasehandler-driver": [
+ "sessions-databasehandler-driver"
+ ],
+ "redishandler-driver": [
+ "sessions-redishandler-driver"
+ ],
+ "memcachedhandler-driver": [
+ "sessions-memcachedhandler-driver"
+ ]
+ },
+ "libraries/throttler.md": {
+ "checkstring-key-int-capacity-int-seconds-int-cost-1": [
+ "check"
+ ],
+ "gettokentime": [
+ "getTokenTime"
+ ],
+ "removestring-key-self": [
+ "remove"
+ ]
+ },
+ "libraries/time.md": {
+ "createfromtimestamp": [
+ "time-createfromtimestamp"
+ ],
+ "todatetimestring": [
+ "time-todatetimestring"
+ ],
+ "ispast": [
+ "time-comparing-two-times-ispast"
+ ],
+ "isfuture": [
+ "time-comparing-two-times-isfuture"
+ ],
+ "viewing-differences": [
+ "time-viewing-differences"
+ ]
+ },
+ "libraries/typography.md": {
+ "autotypographystr-reducelinebreaks-false": [
+ "autoTypography"
+ ],
+ "formatcharactersstr": [
+ "formatCharacters"
+ ],
+ "nl2brexceptprestr": [
+ "nl2brExceptPre"
+ ]
+ },
+ "libraries/uploaded_files.md": {
+ "accessing-files": [
+ "uploaded-files-accessing-files"
+ ],
+ "moving-files": [
+ "uploaded-files-moving-files"
+ ]
+ },
+ "libraries/uri.md": {
+ "gettingsetting-query": [
+ "getting-setting-query"
+ ]
+ },
+ "libraries/user_agent.md": {
+ "codeigniterhttp": [
+ "namespace-CodeIgniter\\HTTP"
+ ],
+ "useragent": [
+ "CodeIgniter\\HTTP\\UserAgent"
+ ],
+ "isbrowserkey-null": [
+ "CodeIgniter\\HTTP\\UserAgent::isBrowser"
+ ],
+ "ismobilekey-null": [
+ "CodeIgniter\\HTTP\\UserAgent::isMobile"
+ ],
+ "isrobotkey-null": [
+ "CodeIgniter\\HTTP\\UserAgent::isRobot"
+ ],
+ "isreferral": [
+ "CodeIgniter\\HTTP\\UserAgent::isReferral"
+ ],
+ "getbrowser": [
+ "CodeIgniter\\HTTP\\UserAgent::getBrowser"
+ ],
+ "getversion": [
+ "CodeIgniter\\HTTP\\UserAgent::getVersion"
+ ],
+ "getmobile": [
+ "CodeIgniter\\HTTP\\UserAgent::getMobile"
+ ],
+ "getrobot": [
+ "CodeIgniter\\HTTP\\UserAgent::getRobot"
+ ],
+ "getplatform": [
+ "CodeIgniter\\HTTP\\UserAgent::getPlatform"
+ ],
+ "getreferrer": [
+ "CodeIgniter\\HTTP\\UserAgent::getReferrer"
+ ],
+ "getagentstring": [
+ "CodeIgniter\\HTTP\\UserAgent::getAgentString"
+ ],
+ "parsestring": [
+ "CodeIgniter\\HTTP\\UserAgent::parse"
+ ]
+ },
+ "libraries/validation.md": {
+ "signupphp": [
+ "signup-php"
+ ],
+ "formphp": [
+ "form-php"
+ ],
+ "traditional-and-strict-rules": [
+ "validation-traditional-and-strict-rules"
+ ],
+ "setting-rules-for-array-data": [
+ "validation-dot-array-syntax"
+ ],
+ "withrequest": [
+ "validation-withrequest"
+ ],
+ "getting-validated-data": [
+ "validation-getting-validated-data"
+ ],
+ "saving-sets-of-validation-rules-to-the-config-file": [
+ "saving-validation-rules-to-config-file"
+ ],
+ "how-to-save-your-rules": [
+ "validation-array"
+ ],
+ "setting-custom-error-messages": [
+ "validation-custom-errors"
+ ],
+ "getting-all-errors": [
+ "validation-getting-all-errors"
+ ],
+ "redirect-and-validation-errors": [
+ "validation-redirect-and-validation-errors"
+ ],
+ "customizing-error-display": [
+ "validation-customizing-error-display"
+ ],
+ "using-rule-classes": [
+ "validation-using-rule-classes"
+ ],
+ "using-closure-rule": [
+ "validation-using-closure-rule"
+ ],
+ "using-callable-rule": [
+ "validation-using-callable-rule"
+ ],
+ "available-rules": [
+ "validation-available-rules"
+ ]
+ },
+ "models/entities.md": {
+ "special-gettersetter": [
+ "entities-special-getter-setter",
+ "special-getter-setter"
+ ],
+ "property-casting": [
+ "entities-property-casting"
+ ],
+ "arrayjson-casting": [
+ "array-json-casting"
+ ]
+ },
+ "models/model.md": {
+ "using-codeigniters-model": [
+ "using-codeigniter-s-model"
+ ],
+ "codeigniters-model": [
+ "codeigniter-s-model"
+ ],
+ "usesoftdeletes": [
+ "model-use-soft-deletes"
+ ],
+ "allowedfields": [
+ "model-allowed-fields"
+ ],
+ "updateonlychanged": [
+ "model-update-only-changed"
+ ],
+ "cleanvalidationrules": [
+ "clean-validation-rules"
+ ],
+ "allowemptyinserts_1": [
+ "model-allow-empty-inserts"
+ ],
+ "replace": [
+ "model-replace"
+ ],
+ "save": [
+ "model-save"
+ ],
+ "saving-dates": [
+ "model-saving-dates"
+ ],
+ "setting-validation-rules": [
+ "model-setting-validation-rules"
+ ],
+ "codeigniter": [
+ "namespace-CodeIgniter"
+ ],
+ "model": [
+ "CodeIgniter\\Model"
+ ],
+ "setvalidationrulefield-fieldrules": [
+ "CodeIgniter\\Model::setValidationRule"
+ ],
+ "setvalidationrulesvalidationrules": [
+ "CodeIgniter\\Model::setValidationRules"
+ ],
+ "setvalidationmessagefield-fieldmessages": [
+ "CodeIgniter\\Model::setValidationMessage"
+ ],
+ "setvalidationmessagesvalidationmessages": [
+ "CodeIgniter\\Model::setValidationMessages"
+ ],
+ "getting-validation-errors": [
+ "model-getting-validation-errors"
+ ],
+ "getting-query-builder-for-the-models-table": [
+ "getting-query-builder-for-the-model-s-table"
+ ],
+ "model-events": [
+ "model-events-callbacks"
+ ]
+ },
+ "outgoing/api_responses.md": {
+ "handling-response-types": [
+ "api-response-trait-handling-response-types"
+ ],
+ "setresponseformatformat": [
+ "setResponseFormat"
+ ],
+ "responddata-statuscode-200-message": [
+ "respond"
+ ],
+ "failmessages-int-status-400-string-code-null-string-message": [
+ "fail"
+ ],
+ "respondcreateddata-null-string-message": [
+ "respondCreated"
+ ],
+ "responddeleteddata-null-string-message": [
+ "respondDeleted"
+ ],
+ "respondnocontentstring-message-no-content": [
+ "respondNoContent"
+ ],
+ "failunauthorizedstring-description-unauthorized-string-code-null-string-message": [
+ "failUnauthorized"
+ ],
+ "failforbiddenstring-description-forbidden-string-codenull-string-message": [
+ "failForbidden"
+ ],
+ "failnotfoundstring-description-not-found-string-codenull-string-message": [
+ "failNotFound"
+ ],
+ "failvalidationerrorserrors-string-codenull-string-message": [
+ "failValidationErrors"
+ ],
+ "failresourceexistsstring-description-conflict-string-codenull-string-message": [
+ "failResourceExists"
+ ],
+ "failresourcegonestring-description-gone-string-codenull-string-message": [
+ "failResourceGone"
+ ],
+ "failtoomanyrequestsstring-description-too-many-requests-string-codenull-string-message": [
+ "failTooManyRequests"
+ ],
+ "failservererrorstring-description-internal-server-error-string-code-null-string-message": [
+ "failServerError"
+ ],
+ "pagination-responses": [
+ "api-response-trait-paginate"
+ ],
+ "paginatemodelbasebuilder-resource-int-perpage-20-string-transformwith-null": [
+ "paginate"
+ ]
+ },
+ "outgoing/api_transformers.md": {
+ "api-resources": [
+ "api-transformers"
+ ],
+ "codeigniterapi": [
+ "namespace-CodeIgniter\\API"
+ ],
+ "basetransformer": [
+ "CodeIgniter\\API\\BaseTransformer"
+ ],
+ "__constructincomingrequest-request-null": [
+ "CodeIgniter\\API\\BaseTransformer::__construct"
+ ],
+ "toarraymixed-resource": [
+ "CodeIgniter\\API\\BaseTransformer::toArray"
+ ],
+ "transformresource-null": [
+ "CodeIgniter\\API\\BaseTransformer::transform"
+ ],
+ "transformmanyarray-resources": [
+ "CodeIgniter\\API\\BaseTransformer::transformMany"
+ ],
+ "getallowedfields": [
+ "CodeIgniter\\API\\BaseTransformer::getAllowedFields"
+ ],
+ "getallowedincludes": [
+ "CodeIgniter\\API\\BaseTransformer::getAllowedIncludes"
+ ],
+ "apiexception": [
+ "CodeIgniter\\API\\ApiException"
+ ],
+ "forinvalidfieldsstring-field": [
+ "CodeIgniter\\API\\ApiException::forInvalidFields"
+ ],
+ "forinvalidincludesstring-include": [
+ "CodeIgniter\\API\\ApiException::forInvalidIncludes"
+ ],
+ "formissingincludestring-include": [
+ "CodeIgniter\\API\\ApiException::forMissingInclude"
+ ]
+ },
+ "outgoing/csp.md": {
+ "clear-directives": [
+ "csp-clear-directives"
+ ],
+ "using-functions": [
+ "csp-using-functions"
+ ]
+ },
+ "outgoing/localization.md": {
+ "in-routes": [
+ "localization-in-routes"
+ ],
+ "synchronizing-translation-files-via-command": [
+ "sync-translations-command"
+ ]
+ },
+ "outgoing/response.md": {
+ "redirect": [
+ "response-redirect"
+ ],
+ "redirect-status-code": [
+ "response-redirect-status-code"
+ ],
+ "codeigniterhttp": [
+ "namespace-CodeIgniter\\HTTP"
+ ],
+ "response": [
+ "CodeIgniter\\HTTP\\Response"
+ ],
+ "getstatuscode": [
+ "CodeIgniter\\HTTP\\Response::getStatusCode"
+ ],
+ "setstatuscodecode-reason": [
+ "CodeIgniter\\HTTP\\Response::setStatusCode"
+ ],
+ "getreasonphrase": [
+ "CodeIgniter\\HTTP\\Response::getReasonPhrase"
+ ],
+ "setdatedate": [
+ "CodeIgniter\\HTTP\\Response::setDate"
+ ],
+ "setcontenttypemime-charsetutf-8": [
+ "CodeIgniter\\HTTP\\Response::setContentType"
+ ],
+ "nocache": [
+ "CodeIgniter\\HTTP\\Response::noCache"
+ ],
+ "setcacheoptions": [
+ "CodeIgniter\\HTTP\\Response::setCache"
+ ],
+ "setlastmodifieddate": [
+ "CodeIgniter\\HTTP\\Response::setLastModified"
+ ],
+ "send-response": [
+ "CodeIgniter\\HTTP\\Response::send"
+ ],
+ "setcookiename-value-expire-0-domain-path-prefix-secure-null-httponly-null-samesite-null": [
+ "CodeIgniter\\HTTP\\Response::setCookie"
+ ],
+ "deletecookiename-domain-path-prefix": [
+ "CodeIgniter\\HTTP\\Response::deleteCookie"
+ ],
+ "hascookiename-value-null-prefix": [
+ "CodeIgniter\\HTTP\\Response::hasCookie"
+ ],
+ "getcookiename-prefix": [
+ "CodeIgniter\\HTTP\\Response::getCookie"
+ ],
+ "getcookies": [
+ "CodeIgniter\\HTTP\\Response::getCookies"
+ ]
+ },
+ "outgoing/table.md": {
+ "synchronizing-rows-with-headings": [
+ "table-sync-rows-with-headings"
+ ],
+ "codeigniterview": [
+ "namespace-CodeIgniter\\View"
+ ],
+ "table": [
+ "CodeIgniter\\View\\Table"
+ ],
+ "generatetabledata-null": [
+ "CodeIgniter\\View\\Table::generate"
+ ],
+ "setcaptioncaption": [
+ "CodeIgniter\\View\\Table::setCaption"
+ ],
+ "setheadingargs": [
+ "CodeIgniter\\View\\Table::setHeading"
+ ],
+ "setfootingargs": [
+ "CodeIgniter\\View\\Table::setFooting"
+ ],
+ "addrowargs": [
+ "CodeIgniter\\View\\Table::addRow"
+ ],
+ "makecolumnsarray-columnlimit-0": [
+ "CodeIgniter\\View\\Table::makeColumns"
+ ],
+ "settemplatetemplate": [
+ "CodeIgniter\\View\\Table::setTemplate"
+ ],
+ "setemptyvalue": [
+ "CodeIgniter\\View\\Table::setEmpty"
+ ],
+ "clear": [
+ "CodeIgniter\\View\\Table::clear"
+ ],
+ "setsyncrowswithheadingbool-orderbykey": [
+ "CodeIgniter\\View\\Table::setSyncRowsWithHeading"
+ ]
+ },
+ "outgoing/view_cells.md": {
+ "calling-a-view-cell": [
+ "app-cells"
+ ],
+ "passing-parameters-as-keyvalue-string": [
+ "passing-parameters-as-key-value-string"
+ ]
+ },
+ "outgoing/view_parser.md": {
+ "restricting-conditionals": [
+ "parser-restricting-conditionals"
+ ],
+ "codeigniterview": [
+ "namespace-CodeIgniter\\View"
+ ],
+ "parser": [
+ "CodeIgniter\\View\\Parser"
+ ],
+ "renderview-options-savedata": [
+ "CodeIgniter\\View\\Parser::render"
+ ],
+ "renderstringtemplate-options-savedata": [
+ "CodeIgniter\\View\\Parser::renderString"
+ ],
+ "setdatadata-context-null": [
+ "CodeIgniter\\View\\Parser::setData"
+ ],
+ "setvarname-value-null-context-null": [
+ "CodeIgniter\\View\\Parser::setVar"
+ ],
+ "setdelimitersleftdelimiter-rightdelimiter": [
+ "CodeIgniter\\View\\Parser::setDelimiters"
+ ],
+ "setconditionaldelimitersleftdelimiter-rightdelimiter": [
+ "CodeIgniter\\View\\Parser::setConditionalDelimiters"
+ ]
+ },
+ "outgoing/view_renderer.md": {
+ "codeigniterview": [
+ "namespace-CodeIgniter\\View"
+ ],
+ "view": [
+ "CodeIgniter\\View\\View"
+ ],
+ "renderview-options-savedata-false": [
+ "CodeIgniter\\View\\View::render"
+ ],
+ "renderstringview-options-savedata-false": [
+ "CodeIgniter\\View\\View::renderString"
+ ],
+ "setdatadata-context-null": [
+ "CodeIgniter\\View\\View::setData"
+ ],
+ "setvarname-value-null-context-null": [
+ "CodeIgniter\\View\\View::setVar"
+ ]
+ },
+ "outgoing/views.md": {
+ "overriding-namespaced-views": [
+ "views-overriding-namespaced-views"
+ ]
+ },
+ "testing/benchmark.md": {
+ "timerstart": [
+ "timer-start"
+ ],
+ "timerstop": [
+ "timer-stop"
+ ],
+ "timerrecord": [
+ "benchmark-timer-record",
+ "timer-record"
+ ]
+ },
+ "testing/cli.md": {
+ "using-mockinputoutput": [
+ "using-mock-input-output"
+ ],
+ "getoutputint-index-null-string": [
+ "getoutput-int-index-null-string"
+ ],
+ "streamfiltertrait": [
+ "stream-filter-trait"
+ ],
+ "citeststreamfilter": [
+ "ci-test-stream-filter"
+ ],
+ "phpstreamwrapper": [
+ "php-stream-wrapper"
+ ]
+ },
+ "testing/controllers.md": {
+ "controllerclass": [
+ "controller-class"
+ ],
+ "executestring-method-params": [
+ "execute-string-method-params"
+ ],
+ "withconfigconfig": [
+ "withconfig-config"
+ ],
+ "withrequestrequest": [
+ "withrequest-request"
+ ],
+ "withresponseresponse": [
+ "withresponse-response"
+ ],
+ "withloggerlogger": [
+ "withlogger-logger"
+ ],
+ "withuristring-uri": [
+ "withuri-string-uri"
+ ],
+ "withbodybody": [
+ "withbody-body"
+ ],
+ "getfiltersforrouteroute-position": [
+ "getFiltersForRoute"
+ ],
+ "getfiltercallerfilter-position": [
+ "getFilterCaller"
+ ]
+ },
+ "testing/database.md": {
+ "seedname": [
+ "seed-name"
+ ],
+ "hasindatabasetable-data": [
+ "hasindatabase-table-data"
+ ],
+ "grabfromdatabasetable-column-criteria": [
+ "grabfromdatabase-table-column-criteria"
+ ],
+ "dontseeindatabasetable-criteria": [
+ "dontseeindatabase-table-criteria"
+ ],
+ "seeindatabasetable-criteria": [
+ "seeindatabase-table-criteria"
+ ],
+ "seenumrecordsexpected-table-criteria": [
+ "seenumrecords-expected-table-criteria"
+ ]
+ },
+ "testing/debugging.md": {
+ "replacing-var_dump": [
+ "replacing-var-dump"
+ ],
+ "hot-reloading": [
+ "debug-toolbar-hot-reload"
+ ]
+ },
+ "testing/fabricator.md": {
+ "getcountstring-table-int": [
+ "getcount-string-table-int"
+ ],
+ "setcountstring-table-int-count-int": [
+ "setcount-string-table-int-count-int"
+ ],
+ "upcountstring-table-int": [
+ "upcount-string-table-int"
+ ],
+ "downcountstring-table-int": [
+ "downcount-string-table-int"
+ ]
+ },
+ "testing/feature.md": {
+ "requesting-a-page": [
+ "feature-requesting-a-page"
+ ],
+ "setting-session-values": [
+ "feature-setting-session-values"
+ ],
+ "formatting-the-request": [
+ "feature-formatting-the-request"
+ ],
+ "setting-the-body": [
+ "feature-setting-the-body"
+ ]
+ },
+ "testing/overview.md": {
+ "traits": [
+ "testing-overview-traits"
+ ],
+ "assertloggedlevel-expectedmessage": [
+ "assertlogged-level-expectedmessage"
+ ],
+ "assertlogcontainslevel-logmessage": [
+ "assertlogcontains-level-logmessage"
+ ],
+ "asserteventtriggeredeventname": [
+ "asserteventtriggered-eventname"
+ ],
+ "assertheaderemittedheader-ignorecase-false": [
+ "assertheaderemitted-header-ignorecase-false"
+ ],
+ "assertheadernotemittedheader-ignorecase-false": [
+ "assertheadernotemitted-header-ignorecase-false"
+ ],
+ "assertcloseenoughexpected-actual-message-tolerance-1": [
+ "assertcloseenough-expected-actual-message-tolerance-1"
+ ],
+ "assertcloseenoughstringexpected-actual-message-tolerance-1": [
+ "assertcloseenoughstring-expected-actual-message-tolerance-1"
+ ],
+ "accessing-protectedprivate-properties": [
+ "accessing-protected-private-properties"
+ ],
+ "getprivatemethodinvokerinstance-method": [
+ "getprivatemethodinvoker-instance-method"
+ ],
+ "getprivatepropertyinstance-property": [
+ "getprivateproperty-instance-property"
+ ],
+ "setprivatepropertyinstance-property-value": [
+ "setprivateproperty-instance-property-value"
+ ],
+ "servicesinjectmock": [
+ "services-injectmock"
+ ],
+ "servicesreset": [
+ "services-reset"
+ ],
+ "servicesresetsinglestring-name": [
+ "services-resetsingle-string-name"
+ ]
+ },
+ "testing/response.md": {
+ "accessing-requestresponse": [
+ "accessing-request-response"
+ ],
+ "assertstatusint-code": [
+ "assertstatus-int-code"
+ ],
+ "session-assertions": [
+ "response-session-assertions"
+ ],
+ "assertsessionhasstring-key-value-null": [
+ "assertsessionhas-string-key-value-null"
+ ],
+ "assertsessionmissingstring-key": [
+ "assertsessionmissing-string-key"
+ ],
+ "assertheaderstring-key-value-null": [
+ "assertheader-string-key-value-null"
+ ],
+ "assertheadermissingstring-key": [
+ "assertheadermissing-string-key"
+ ],
+ "assertcookiestring-key-value-null-string-prefix": [
+ "assertcookie-string-key-value-null-string-prefix"
+ ],
+ "assertcookiemissingstring-key": [
+ "assertcookiemissing-string-key"
+ ],
+ "assertcookieexpiredstring-key-string-prefix": [
+ "assertcookieexpired-string-key-string-prefix"
+ ],
+ "assertseestring-search-null-string-element-null": [
+ "assertsee-string-search-null-string-element-null"
+ ],
+ "assertdontseestring-search-null-string-element-null": [
+ "assertdontsee-string-search-null-string-element-null"
+ ],
+ "assertseeelementstring-search": [
+ "assertseeelement-string-search"
+ ],
+ "assertdontseeelementstring-search": [
+ "assertdontseeelement-string-search"
+ ],
+ "assertseelinkstring-text-string-details-null": [
+ "assertseelink-string-text-string-details-null"
+ ],
+ "assertseeinfieldstring-field-string-value-null": [
+ "assertseeinfield-string-field-string-value-null"
+ ],
+ "assertjsonfragmentarray-fragment": [
+ "assertjsonfragment-array-fragment"
+ ],
+ "assertjsonexacttest": [
+ "assertjsonexact-test"
+ ]
+ }
+}
diff --git a/docs/.nav.yml b/docs/.nav.yml
new file mode 100644
index 000000000000..ab7bb8d95e85
--- /dev/null
+++ b/docs/.nav.yml
@@ -0,0 +1,346 @@
+nav:
+ - Home: index.md
+ - "Welcome to CodeIgniter4":
+ - intro/index.md
+ - intro/requirements.md
+ - intro/credits.md
+ - intro/psr.md
+ - "License Agreement": license.md
+ - "Installation":
+ - installation/index.md
+ - installation/installing_composer.md
+ - installation/installing_manual.md
+ - installation/running.md
+ - installation/worker_mode.md
+ - installation/troubleshooting.md
+ - installation/deployment.md
+ - "Change Logs":
+ - changelogs/index.md
+ - changelogs/v4.7.7.md
+ - changelogs/v4.7.6.md
+ - changelogs/v4.7.5.md
+ - changelogs/v4.7.4.md
+ - changelogs/v4.7.3.md
+ - changelogs/v4.7.2.md
+ - changelogs/v4.7.1.md
+ - changelogs/v4.7.0.md
+ - changelogs/v4.6.5.md
+ - changelogs/v4.6.4.md
+ - changelogs/v4.6.3.md
+ - changelogs/v4.6.2.md
+ - changelogs/v4.6.1.md
+ - changelogs/v4.6.0.md
+ - changelogs/v4.5.8.md
+ - changelogs/v4.5.7.md
+ - changelogs/v4.5.6.md
+ - changelogs/v4.5.5.md
+ - changelogs/v4.5.4.md
+ - changelogs/v4.5.3.md
+ - changelogs/v4.5.2.md
+ - changelogs/v4.5.1.md
+ - changelogs/v4.5.0.md
+ - changelogs/v4.4.8.md
+ - changelogs/v4.4.7.md
+ - changelogs/v4.4.6.md
+ - changelogs/v4.4.5.md
+ - changelogs/v4.4.4.md
+ - changelogs/v4.4.3.md
+ - changelogs/v4.4.2.md
+ - changelogs/v4.4.1.md
+ - changelogs/v4.4.0.md
+ - changelogs/v4.3.8.md
+ - changelogs/v4.3.7.md
+ - changelogs/v4.3.6.md
+ - changelogs/v4.3.5.md
+ - changelogs/v4.3.4.md
+ - changelogs/v4.3.3.md
+ - changelogs/v4.3.2.md
+ - changelogs/v4.3.1.md
+ - changelogs/v4.3.0.md
+ - changelogs/v4.2.12.md
+ - changelogs/v4.2.11.md
+ - changelogs/v4.2.10.md
+ - changelogs/v4.2.9.md
+ - changelogs/v4.2.8.md
+ - changelogs/v4.2.7.md
+ - changelogs/v4.2.6.md
+ - changelogs/v4.2.5.md
+ - changelogs/v4.2.4.md
+ - changelogs/v4.2.3.md
+ - changelogs/v4.2.2.md
+ - changelogs/v4.2.1.md
+ - changelogs/v4.2.0.md
+ - changelogs/v4.1.9.md
+ - changelogs/v4.1.8.md
+ - changelogs/v4.1.7.md
+ - changelogs/v4.1.6.md
+ - changelogs/v4.1.5.md
+ - changelogs/v4.1.4.md
+ - changelogs/v4.1.3.md
+ - changelogs/v4.1.2.md
+ - changelogs/v4.1.1.md
+ - changelogs/v4.1.0.md
+ - changelogs/v4.0.5.md
+ - changelogs/v4.0.4.md
+ - changelogs/v4.0.3.md
+ - changelogs/v4.0.0.md
+ - changelogs/v4.0.0-rc.4.md
+ - changelogs/v4.0.0-rc.3.md
+ - changelogs/v4.0.0-rc.2.md
+ - changelogs/v4.0.0-rc.1.md
+ - changelogs/v4.0.0-beta.4.md
+ - changelogs/v4.0.0-beta.3.md
+ - changelogs/v4.0.0-beta.2.md
+ - changelogs/v4.0.0-beta.1.md
+ - changelogs/v4.0.0-alpha.5.md
+ - changelogs/v4.0.0-alpha.4.md
+ - changelogs/v4.0.0-alpha.3.md
+ - changelogs/v4.0.0-alpha.2.md
+ - changelogs/v4.0.0-alpha.1.md
+ - "Upgrading From a Previous Version":
+ - installation/upgrading.md
+ - installation/backward_compatibility_notes.md
+ - installation/upgrade_477.md
+ - installation/upgrade_476.md
+ - installation/upgrade_475.md
+ - installation/upgrade_474.md
+ - installation/upgrade_473.md
+ - installation/upgrade_472.md
+ - installation/upgrade_471.md
+ - installation/upgrade_470.md
+ - installation/upgrade_465.md
+ - installation/upgrade_464.md
+ - installation/upgrade_463.md
+ - installation/upgrade_462.md
+ - installation/upgrade_461.md
+ - installation/upgrade_460.md
+ - installation/upgrade_458.md
+ - installation/upgrade_457.md
+ - installation/upgrade_456.md
+ - installation/upgrade_455.md
+ - installation/upgrade_454.md
+ - installation/upgrade_453.md
+ - installation/upgrade_452.md
+ - installation/upgrade_451.md
+ - installation/upgrade_450.md
+ - installation/upgrade_448.md
+ - installation/upgrade_447.md
+ - installation/upgrade_446.md
+ - installation/upgrade_445.md
+ - installation/upgrade_444.md
+ - installation/upgrade_443.md
+ - installation/upgrade_442.md
+ - installation/upgrade_441.md
+ - installation/upgrade_440.md
+ - installation/upgrade_438.md
+ - installation/upgrade_437.md
+ - installation/upgrade_436.md
+ - installation/upgrade_435.md
+ - installation/upgrade_434.md
+ - installation/upgrade_433.md
+ - installation/upgrade_432.md
+ - installation/upgrade_431.md
+ - installation/upgrade_430.md
+ - installation/upgrade_4212.md
+ - installation/upgrade_4211.md
+ - installation/upgrade_4210.md
+ - installation/upgrade_428.md
+ - installation/upgrade_427.md
+ - installation/upgrade_426.md
+ - installation/upgrade_425.md
+ - installation/upgrade_423.md
+ - installation/upgrade_422.md
+ - installation/upgrade_421.md
+ - installation/upgrade_420.md
+ - installation/upgrade_418.md
+ - installation/upgrade_417.md
+ - installation/upgrade_416.md
+ - installation/upgrade_415.md
+ - installation/upgrade_414.md
+ - installation/upgrade_413.md
+ - installation/upgrade_412.md
+ - installation/upgrade_410.md
+ - installation/upgrade_405.md
+ - installation/upgrade_404.md
+ - "Upgrading from 3.x to 4.x":
+ - installation/upgrade_4xx.md
+ - installation/upgrade_models.md
+ - installation/upgrade_views.md
+ - installation/upgrade_controllers.md
+ - installation/upgrade_configuration.md
+ - installation/upgrade_database.md
+ - installation/upgrade_emails.md
+ - installation/upgrade_encryption.md
+ - installation/upgrade_file_upload.md
+ - installation/upgrade_html_tables.md
+ - installation/upgrade_images.md
+ - installation/upgrade_localization.md
+ - installation/upgrade_migrations.md
+ - installation/upgrade_responses.md
+ - installation/upgrade_pagination.md
+ - installation/upgrade_routing.md
+ - installation/upgrade_security.md
+ - installation/upgrade_sessions.md
+ - installation/upgrade_validations.md
+ - installation/upgrade_view_parser.md
+ - installation/repositories.md
+ - "Guides":
+ - guides/index.md
+ - "Build Your First Application":
+ - guides/first-app/index.md
+ - guides/first-app/static_pages.md
+ - guides/first-app/news_section.md
+ - guides/first-app/create_news_items.md
+ - guides/first-app/conclusion.md
+ - "Getting Started with REST APIs":
+ - guides/api/index.md
+ - guides/api/first-endpoint.md
+ - guides/api/database-setup.md
+ - guides/api/controller.md
+ - guides/api/conclusion.md
+ - "CodeIgniter4 Overview":
+ - concepts/index.md
+ - concepts/structure.md
+ - concepts/mvc.md
+ - concepts/autoloader.md
+ - concepts/services.md
+ - concepts/factories.md
+ - concepts/http.md
+ - concepts/security.md
+ - concepts/goals.md
+ - "General Topics":
+ - general/index.md
+ - general/configuration.md
+ - general/urls.md
+ - general/helpers.md
+ - general/common_functions.md
+ - general/logging.md
+ - general/errors.md
+ - general/caching.md
+ - general/ajax.md
+ - general/modules.md
+ - general/managing_apps.md
+ - general/environments.md
+ - "Controllers and Routing":
+ - incoming/index.md
+ - incoming/routing.md
+ - incoming/controllers.md
+ - incoming/filters.md
+ - incoming/controller_attributes.md
+ - incoming/auto_routing_improved.md
+ - incoming/message.md
+ - incoming/request.md
+ - incoming/incomingrequest.md
+ - incoming/content_negotiation.md
+ - incoming/methodspoofing.md
+ - incoming/restful.md
+ - "Building Responses":
+ - outgoing/index.md
+ - outgoing/views.md
+ - outgoing/view_renderer.md
+ - outgoing/view_layouts.md
+ - outgoing/view_cells.md
+ - outgoing/view_parser.md
+ - outgoing/view_decorators.md
+ - outgoing/table.md
+ - outgoing/response.md
+ - outgoing/api_responses.md
+ - outgoing/api_transformers.md
+ - outgoing/csp.md
+ - outgoing/localization.md
+ - outgoing/alternative_php.md
+ - "Working with Databases":
+ - database/index.md
+ - "Quick Start: Usage Examples": database/examples.md
+ - "Database Configuration": database/configuration.md
+ - "Connecting to a Database": database/connecting.md
+ - "Running Queries": database/queries.md
+ - "Generating Query Results": database/results.md
+ - "Query Helper Methods": database/helpers.md
+ - "Query Builder Class": database/query_builder.md
+ - "Transactions": database/transactions.md
+ - "Getting Metadata": database/metadata.md
+ - "Custom Function Calls": database/call_function.md
+ - "Database Events": database/events.md
+ - "Database Utilities": database/utilities.md
+ - "Modeling Data":
+ - models/index.md
+ - "Using CodeIgniter's Model": models/model.md
+ - "Using Entity Classes": models/entities.md
+ - "Managing Databases":
+ - dbmgmt/index.md
+ - "Database Forge": dbmgmt/forge.md
+ - "Database Migrations": dbmgmt/migration.md
+ - "Database Seeding": dbmgmt/seeds.md
+ - dbmgmt/db_commands.md
+ - "Library Reference":
+ - libraries/index.md
+ - libraries/caching.md
+ - libraries/cookies.md
+ - libraries/cors.md
+ - libraries/curlrequest.md
+ - libraries/email.md
+ - libraries/encryption.md
+ - libraries/files.md
+ - libraries/file_collections.md
+ - libraries/honeypot.md
+ - libraries/images.md
+ - libraries/pagination.md
+ - libraries/publisher.md
+ - libraries/security.md
+ - libraries/sessions.md
+ - libraries/throttler.md
+ - libraries/time.md
+ - libraries/typography.md
+ - libraries/uploaded_files.md
+ - libraries/uri.md
+ - libraries/user_agent.md
+ - libraries/validation.md
+ - "Helpers":
+ - helpers/index.md
+ - helpers/array_helper.md
+ - helpers/cookie_helper.md
+ - helpers/date_helper.md
+ - helpers/filesystem_helper.md
+ - helpers/form_helper.md
+ - helpers/html_helper.md
+ - helpers/inflector_helper.md
+ - helpers/number_helper.md
+ - helpers/security_helper.md
+ - helpers/test_helper.md
+ - helpers/text_helper.md
+ - helpers/url_helper.md
+ - helpers/xml_helper.md
+ - "Testing":
+ - testing/index.md
+ - "Getting Started": testing/overview.md
+ - "Database": testing/database.md
+ - "Generating Data": testing/fabricator.md
+ - "Controller Testing": testing/controllers.md
+ - "HTTP Testing": testing/feature.md
+ - testing/response.md
+ - testing/cli.md
+ - "Mocking": testing/mocking.md
+ - testing/benchmark.md
+ - testing/debugging.md
+ - "Session Testing": testing/session_testing.md
+ - "Command Line Usage":
+ - cli/index.md
+ - cli/cli_overview.md
+ - cli/cli_controllers.md
+ - cli/spark_commands.md
+ - cli/cli_commands.md
+ - cli/cli_generators.md
+ - cli/cli_library.md
+ - cli/cli_signals.md
+ - cli/cli_request.md
+ - "Extending CodeIgniter":
+ - extending/index.md
+ - extending/core_classes.md
+ - extending/common.md
+ - extending/events.md
+ - extending/basecontroller.md
+ - extending/authentication.md
+ - extending/composer_packages.md
+ - extending/contributing.md
+ - libraries/official_packages.md
diff --git a/docs/changelogs/index.md b/docs/changelogs/index.md
new file mode 100644
index 000000000000..3f8f322cf192
--- /dev/null
+++ b/docs/changelogs/index.md
@@ -0,0 +1,89 @@
+# Change Logs
+
+## Version 4.7
+
+**List of all CodeIgniter4 releases**
+
+See all the changes.
+
+- [Version 4.7.7](v4.7.7.md)
+- [Version 4.7.6](v4.7.6.md)
+- [Version 4.7.5](v4.7.5.md)
+- [Version 4.7.4](v4.7.4.md)
+- [Version 4.7.3](v4.7.3.md)
+- [Version 4.7.2](v4.7.2.md)
+- [Version 4.7.1](v4.7.1.md)
+- [Version 4.7.0](v4.7.0.md)
+- [Version 4.6.5](v4.6.5.md)
+- [Version 4.6.4](v4.6.4.md)
+- [Version 4.6.3](v4.6.3.md)
+- [Version 4.6.2](v4.6.2.md)
+- [Version 4.6.1](v4.6.1.md)
+- [Version 4.6.0](v4.6.0.md)
+- [Version 4.5.8](v4.5.8.md)
+- [Version 4.5.7](v4.5.7.md)
+- [Version 4.5.6](v4.5.6.md)
+- [Version 4.5.5](v4.5.5.md)
+- [Version 4.5.4](v4.5.4.md)
+- [Version 4.5.3](v4.5.3.md)
+- [Version 4.5.2](v4.5.2.md)
+- [Version 4.5.1](v4.5.1.md)
+- [Version 4.5.0](v4.5.0.md)
+- [Version 4.4.8](v4.4.8.md)
+- [Version 4.4.7](v4.4.7.md)
+- [Version 4.4.6](v4.4.6.md)
+- [Version 4.4.5](v4.4.5.md)
+- [Version 4.4.4](v4.4.4.md)
+- [Version 4.4.3](v4.4.3.md)
+- [Version 4.4.2](v4.4.2.md)
+- [Version 4.4.1](v4.4.1.md)
+- [Version 4.4.0](v4.4.0.md)
+- [Version 4.3.8](v4.3.8.md)
+- [Version 4.3.7](v4.3.7.md)
+- [Version 4.3.6](v4.3.6.md)
+- [Version 4.3.5](v4.3.5.md)
+- [Version 4.3.4](v4.3.4.md)
+- [Version 4.3.3](v4.3.3.md)
+- [Version 4.3.2](v4.3.2.md)
+- [Version 4.3.1](v4.3.1.md)
+- [Version 4.3.0](v4.3.0.md)
+- [Version 4.2.12](v4.2.12.md)
+- [Version 4.2.11](v4.2.11.md)
+- [Version 4.2.10](v4.2.10.md)
+- [Version 4.2.9](v4.2.9.md)
+- [Version 4.2.8](v4.2.8.md)
+- [Version 4.2.7](v4.2.7.md)
+- [Version 4.2.6](v4.2.6.md)
+- [Version 4.2.5](v4.2.5.md)
+- [Version 4.2.4](v4.2.4.md)
+- [Version 4.2.3](v4.2.3.md)
+- [Version 4.2.2](v4.2.2.md)
+- [Version 4.2.1](v4.2.1.md)
+- [Version 4.2.0](v4.2.0.md)
+- [Version 4.1.9](v4.1.9.md)
+- [Version 4.1.8](v4.1.8.md)
+- [Version 4.1.7](v4.1.7.md)
+- [Version 4.1.6](v4.1.6.md)
+- [Version 4.1.5](v4.1.5.md)
+- [Version 4.1.4](v4.1.4.md)
+- [Version 4.1.3](v4.1.3.md)
+- [Version 4.1.2](v4.1.2.md)
+- [Version 4.1.1](v4.1.1.md)
+- [Version 4.1.0](v4.1.0.md)
+- [Version 4.0.5](v4.0.5.md)
+- [Version 4.0.4](v4.0.4.md)
+- [Version 4.0.3](v4.0.3.md)
+- [Version 4.0.0](v4.0.0.md)
+- [Version 4.0.0-rc.4](v4.0.0-rc.4.md)
+- [Version 4.0.0-rc.3](v4.0.0-rc.3.md)
+- [Version 4.0.0-rc.2](v4.0.0-rc.2.md)
+- [Version 4.0.0-rc.1](v4.0.0-rc.1.md)
+- [Version 4.0.0-beta.4](v4.0.0-beta.4.md)
+- [Version 4.0.0-beta.3](v4.0.0-beta.3.md)
+- [Version 4.0.0-beta.2](v4.0.0-beta.2.md)
+- [Version 4.0.0-beta.1](v4.0.0-beta.1.md)
+- [Version 4.0.0-alpha.5](v4.0.0-alpha.5.md)
+- [Version 4.0.0-alpha.4](v4.0.0-alpha.4.md)
+- [Version 4.0.0-alpha.3](v4.0.0-alpha.3.md)
+- [Version 4.0.0-alpha.2](v4.0.0-alpha.2.md)
+- [Version 4.0.0-alpha.1](v4.0.0-alpha.1.md)
diff --git a/docs/changelogs/v4.0.0-alpha.1.md b/docs/changelogs/v4.0.0-alpha.1.md
new file mode 100644
index 000000000000..69915b2b3404
--- /dev/null
+++ b/docs/changelogs/v4.0.0-alpha.1.md
@@ -0,0 +1,107 @@
+# Version 4.0.0-alpha.1
+
+Release Date: September 28, 2018
+
+**Rewrite of the CodeIgniter framework**
+
+## New Packages List
+
+```
+.
+├── API/
+│ └── ResponseTrait
+├── Autoloader/
+│ └── AutoLoader, FileLocator
+├── CLI/
+│ └── BaseCommand, CLI, CommandRunner, Console
+├── Cache/
+│ ├── CacheFactory, CacheInterface
+│ └── Handlers/
+│ └── Dummy, File, Memcached, Predis, Redis, Wincache
+├── Commands/
+│ ├── Help, ListCommands
+│ ├── Database/
+│ │ └── CreateMigration, MigrateCurrent, MigrateLatest, MigrateRefresh, MigrateRollback, MigrateStatus,
+│ │ MigrateVersion, Seed
+│ ├── Server/
+│ │ └── Serve
+│ ├── Sessions/
+│ │ └── CreateMigration
+│ └── Utilities/
+│ └── Namespaces, Routes
+├── Config/
+│ └── AutoloadConfig, BaseConfig, BaseService, Config, DotEnv, ForeignCharacters, Routes, Services, View
+├── Database/
+│ ├── BaseBuilder, BaseConnection, BasePreparedQuery, BaseResult, BaseUtils, Config, ConnectionInterface,
+│ │ Database, Forge, Migration, MigrationRunner, PreparedQueryInterface, Query, QueryInterface,
+│ │ ResultInterface, Seeder
+│ ├── MySQLi/
+│ │ └── Builder, Connection, Forge, PreparedQuery, Result
+│ ├── Postgre/
+│ │ └── Builder, Connection, Forge, PreparedQuery, Result, Utils
+│ └── SQLite3/
+│ └── Builder, Connection, Forge, PreparedQuery, Result, Utils
+├── Debug/
+│ ├── Exceptions, Iterator, Timer, Toolbar
+│ └── Toolbar/
+│ └── Collectors...
+├── Email/
+│ └── Email
+├── Events/
+│ └── Events
+├── Files/
+│ └── File
+├── Filters/
+│ └── FilterInterface, Filters
+├── Format/
+│ └── FormatterInterface, JSONFormatter, XMLFormatter
+├── HTTP/
+│ ├── CLIRequest, CURLRequest, ContentSecurityPolicy, Header, IncomingRequest, Message, Negotiate, Request,
+│ │ RequestInterface, Response, ResponseInterface, URI, UserAgent
+│ └── Files/
+│ └── FileCollection, UploadedFile, UploadedFileInterface
+├── Helpers/
+│ └── array, cookie, date, filesystem, form, html, inflector, number, security, text, url
+├── Honeypot/
+│ └── Honeypot
+├── I18n/
+│ └── Time, TimeDifference
+├── Images/
+│ ├── Image, ImageHandlerInterface
+│ └── Handlers/
+│ └── Base, GD, ImageMagick
+├── Language/
+│ └── Language
+├── Log/
+│ ├── Logger, LoggerAwareTrait
+│ └── Handlers/
+│ └── Base, ChromeLogger, File, HandlerInterface
+├── Pager/
+│ └── Pager, PagerInterface, PagerRenderer
+├── Router/
+│ └── RouteCollection, RouteCollectionInterface, Router, RouterInterface
+├── Security/
+│ └── Security
+├── Session/
+│ ├── Session, SessionInterface
+│ └── Handlers/
+│ └── Base, File, Memcached, Redis
+├── Test/
+│ ├── CIDatabaseTestCase, CIUnitTestCase, FeatureResponse, FeatureTestCase, ReflectionHelper
+│ └── Filters/
+│ └── CITestStreamFilter
+├── ThirdParty/ (bundled)
+│ ├── Kint (for Debug)
+│ ├── PSR/
+│ │ └── Log (for Log)
+│ └── ZendEscaper/
+│ └── Escaper (for View)
+├── Throttle/
+│ └── Throttler, ThrottlerInterface
+├── Typography/
+│ └── Typography
+├── Validation/
+│ └── CreditCardRules, FileRules, FormatRules, Rules, Validation, ValidationInterface
+└── View/
+ └── Cell, Filters, Parser, Plugins, RendererInterface, View
+```
diff --git a/docs/changelogs/v4.0.0-alpha.2.md b/docs/changelogs/v4.0.0-alpha.2.md
new file mode 100644
index 000000000000..12d96ade43b7
--- /dev/null
+++ b/docs/changelogs/v4.0.0-alpha.2.md
@@ -0,0 +1,195 @@
+# Version 4.0.0-alpha.2
+
+Release Date: Oct 26, 2018
+
+**Second alpha release of CodeIgniter4**
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── application/
+│ ├── composer.json #1312
+│ ├── Config/Boot/development, production, testing #1312
+│ ├── Config/Paths #1341
+│ ├── Config/Routes #1281
+│ ├── Filters/Honeypot #1314
+│ ├── Views/errors/cli/error_404 #1272
+│ └── Views/welcome_message #1342
+├── public/
+│ ├── .htaccess #1281
+│ └── index #1295, #1313
+├── system/
+│ ├── CLI/
+│ │ └── CommandRunner #1350, #1356
+│ ├── Commands/
+│ │ └── Server/Serve #1313
+│ ├── Config/
+│ │ ├── AutoloadConfig #1271
+│ │ └── Services #1341
+│ ├── Database/
+│ │ ├── BaseBuilder #1217
+│ │ ├── BaseUtils #1209, #1329
+│ │ ├── Database #1339
+│ │ └── MySQLi/Utils #1209
+│ ├── Debug/Toolbar/
+│ │ └── Views/toolbar.css #1342
+│ ├── Exceptions/
+│ │ ├── CastException #1283
+│ │ ├── DownloadException #1239
+│ │ └── FrameworkException #1313
+│ ├── Filters/
+│ │ └── Filters #1239
+│ ├── Helpers/
+│ │ ├── cookie_helper #1286
+│ │ ├── form_helper #1244, #1327
+│ │ ├── url_helper #1321
+│ │ └── xml_helper #1209
+│ ├── Honeypot/
+│ │ └── Honeypot #1314
+│ ├── HTTP/
+│ │ ├── CliRequest #1303
+│ │ ├── CURLRequest #1303
+│ │ ├── DownloadResponse #1239
+│ │ ├── Exceptions/HTTPException #1303
+│ │ ├── IncomingRequest #1304, #1313
+│ │ ├── Negotiate #1306
+│ │ ├── RedirectResponse #1300, #1306, #1329
+│ │ ├── Response #1239, #1286
+│ │ ├── ResponseInterface #1239
+│ │ └── URI #1300
+│ ├── Language/en/
+│ │ ├── Cast #1283
+│ │ └── HTTP #1239
+│ ├── Router/
+│ │ └── RouteCollection #1285, #1355
+│ ├── Test/
+│ │ ├── CIUnitTestCase #1312, #1361
+│ │ └── FeatureTestCase #1282
+│ ├── CodeIgniter #1239 #1337
+│ ├── Common #1291
+│ ├── Entity #1283, #1311
+│ └── Model #1311
+├── tests/
+│ ├── API/
+│ │ └── ResponseTraitTest #1302
+│ ├── Commands/
+│ │ └── CommandsTest #1356
+│ ├── Database/
+│ │ ├── BaseBuilderTest #1217
+│ │ └── Live/ModelTest #1311
+│ ├── Debug/
+│ │ └── TimerTest #1273
+│ ├── Helpers/
+│ │ └── CookieHelperTest #1286
+│ ├── Honeypot/
+│ │ └── HoneypotTest #1314
+│ ├── HTTP/
+│ │ ├── Files/
+│ │ │ ├── FileMovingTest #1302
+│ │ │ └── UploadedFileTest #1302
+│ │ ├── CLIRequestTest #1303
+│ │ ├── CURLRequestTest #1303
+│ │ ├── DownloadResponseTest #1239
+│ │ ├── NegotiateTest #1306
+│ │ ├── RedirectResponseTest #1300, #1306, #1329
+│ │ └── ResponseTest #1239
+│ ├── I18n/
+│ │ └── TimeTest #1273, #1316
+│ ├── Router/
+│ │ └── RouteTest #1285, #1355
+│ ├── Test/
+│ │ ├── TestCaseEmissionsTest #1312
+│ │ └── TestCaseTest #1312
+│ ├── View/
+│ │ └── ParserTest #1311
+│ └── EntityTest #1319
+├── user_guide_src/source/
+│ ├── cli/
+│ │ └── cli_request #1303
+│ ├── database/
+│ │ ├── query_builder #1217
+│ │ └── utilities #1209
+│ ├── extending/
+│ │ └── contributing #1280
+│ ├── general/
+│ │ ├── common_functions #1300, #1329
+│ │ ├── helpers #1291
+│ │ └── managing_apps #1341
+│ ├── helpers/
+│ │ └── xml_helper #1321
+│ ├── incoming/
+│ │ ├── controllers #1323
+│ │ └── routing #1337
+│ ├── intro/
+│ │ └── requirements #1280, #1303
+│ ├── installation/ #1280, #1303
+│ │ └── troubleshooting #1265
+│ ├── libraries/
+│ │ ├── curlrequest #1303
+│ │ ├── honeypot #1314
+│ │ ├── sessions #1333
+│ │ └── uploaded_files #1302
+│ ├── models/
+│ │ └── entities #1283
+│ ├── outgoing/
+│ │ └── response #1340
+│ ├── testing/
+│ │ └── overview #1312
+│ └── tutorial... #1265, #1281, #1294
+└── spark #1305
+```
+
+## PRs merged
+
+- \#1361 Add timing assertion to CIUnitTestCase
+- \#1312 Add headerEmitted assertions to CIUnitTestCase
+- \#1356 Testing/commands
+- \#1355 Handle duplicate HTTP verb and generic rules properly
+- \#1350 Checks if class is instantiable and is a command
+- \#1348 Fix sphinx formatting in sessions
+- \#1347 Fix sphinx formatting in sessions
+- \#1342 Toolbar Styles
+- \#1341 Make viewpath configurable in Paths.php. Fixes #1296
+- \#1340 Update docs for downloads to reflect the need to return it. Fixes #1331
+- \#1339 Fix error where Forge class might not be returned. Fixes #1225
+- \#1337 Filter in the router Fixes #1315
+- \#1336 Revert alpha.2
+- \#1334 Proposed changelog for alpha.2
+- \#1333 Error in user guide for session config. Fixes #1330
+- \#1329 Tweaks
+- \#1327 FIX form_hidden and form_open - value escaping as is in form_input.
+- \#1323 Fix doc error: show_404() doesn't exist any more
+- \#1321 Added missing xml_helper UG page
+- \#1319 Testing/entity
+- \#1316 Refactor TimeTest
+- \#1314 Fix & expand Honeypot & its tests
+- \#1313 Clean exception
+- \#1311 Entities store an original stack of values to compare against so we d...
+- \#1306 Testing3/http
+- \#1305 Change chdir('public') to chdir($public)
+- \#1304 Refactor script name stripping in parseRequestURI()
+- \#1303 Testing/http
+- \#1302 Exception: No Formatter defined for mime type ''
+- \#1300 Allow redirect with Query Vars from the current request.
+- \#1295 Fix grammar in front controller comment.
+- \#1294 Updated final tutorial page. Fixes #1292
+- \#1291 Allows extending of helpers. Fixes #1264
+- \#1286 Cookies
+- \#1285 Ensure current HTTP verb routes are matched prior to any \* matched ro...
+- \#1283 Entities
+- \#1282 system/Test/FeatureTestCase::setupRequest(), minor fixes phpdoc block...
+- \#1281 Tut
+- \#1280 Add contributing reference to user guide
+- \#1273 Fix/timing
+- \#1272 Fix undefined variable "heading" in cli 404
+- \#1271 remove nonexistent "CodeIgniterLoader" from AutoloadConfig::classmap
+- \#1269 Release notes & process
+- \#1266 Adjusting the release build scripts
+- \#1265 WIP Fix docs re PHP server
+- \#1245 Fix #1244 (form_hidden declaration)
+- \#1239 [Unsolicited PR] I changed the download method to testable.
+- \#1217 Optional parameter for resetSelect() call in Builder's countAll();
+- \#1209 Fix undefined function xml_convert at DatabaseBaseUtils
diff --git a/docs/changelogs/v4.0.0-alpha.3.md b/docs/changelogs/v4.0.0-alpha.3.md
new file mode 100644
index 000000000000..cbae1b28e06a
--- /dev/null
+++ b/docs/changelogs/v4.0.0-alpha.3.md
@@ -0,0 +1,374 @@
+# Version 4.0.0-alpha.3
+
+Release Date: November 30, 2018
+
+**Next alpha release of CodeIgniter4**
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── admin/
+│ ├── framework/* #1553
+│ ├── starter/* #1553
+│ ├── docbot #1553
+│ ├── release* #1484,
+│ ├── pre-commit #1388
+│ ├── README.md #1553
+│ └── setup.sh #1388
+├── application/
+│ ├── Config/
+│ │ ├── Autoload #1396, #1416
+│ │ ├── Mimes #1368, #1465
+│ │ ├── Pager #622
+│ │ └── Services #1469
+│ ├── Filters/Honeypot #1376
+│ └── Views/
+│ ├── errors/* #1415, #1413, #1469
+│ └── form.php removed #1442
+├── public/
+│ └── index.php #1388, #1457
+├── system/
+│ ├── Autoloader/
+│ │ ├── Autoloader #1547
+│ │ └── FileLocator #1547, #1550
+│ ├── Cache/
+│ │ ├── Exceptions/CacheException #1525
+│ │ ├── Handlers/FileHandler #1547, #1525
+│ │ └── Handlers/MemcachedHandler #1383
+│ ├── CLI/
+│ │ └── CLI #1432, #1489
+│ ├── Commands/
+│ │ ├── Database/
+│ │ │ ├── CreateMigration #1374, #1422, #1431
+│ │ │ ├── MigrateCurrent #1431
+│ │ │ ├── MigrateLatest #1431
+│ │ │ ├── MigrateRollback #1431
+│ │ │ ├── MigrateStatus #1431
+│ │ │ └── MigrateVersion #1431
+│ │ └── Sessions/CreateMigration #1357
+│ ├── Config/
+│ │ ├── AutoloadConfig #1416
+│ │ ├── BaseService #1469
+│ │ ├── Mimes #1453
+│ │ └── Services #1180, #1469
+│ ├── Database/
+│ │ ├── BaseBuilder #1335, #1491, #1522
+│ │ ├── BaseConnection #1335, #1407, #1491, #1522
+│ │ ├── BaseResult #1426
+│ │ ├── Config #1465, #1469, #1554
+│ │ ├── Forge #1343, #1449, #1470, #1530
+│ │ ├── MigrationRunner #1371
+│ │ ├── MySQLi/Connection #1335, #1449
+│ │ ├── MySQLi/Forge #1343, #1344, #1530
+│ │ ├── MySQLi/Result #1530
+│ │ ├── Postgre/Connection #1335, #1449
+│ │ ├── Postgre/Forge #1530
+│ │ ├── SQLite3/Connection #1335, #1449
+│ │ └── SQLite3/Forge #1470, #1547
+│ ├── Debug
+│ │ ├── Exceptions #1500
+│ │ ├── Toolbar #1370, #1465, #1469, #1547
+│ │ └── Toolbar/Views/toolbar.tpl #1469
+│ ├── Email/
+│ │ └── Email #1389, #1413, #1438, #1454, #1465, #1469, #1547
+│ ├── Events/
+│ │ └── Events #1465, #1469, #1547
+│ ├── Files/
+│ │ └── File #1399, #1547
+│ ├── Format/
+│ │ └── XMLFormatter #1471
+│ ├── Helpers/
+│ │ ├── array_helper #1412
+│ │ └── filesystem_helper #1547
+│ ├── Honeypot/
+│ │ └── Honeypot #1460
+│ ├── HTTP/
+│ │ ├── CURLRequest #1547, #1498
+│ │ ├── DownloadResponse #1375
+│ │ ├── Exceptions/DownloadException #1405
+│ │ ├── Files/FileCollection #1506
+│ │ ├── Files/UploadedFile #1335, #1399, #1500, #1506, #1547
+│ │ ├── IncomingRequest #1445, #1469, #1496
+│ │ ├── Message #1497
+│ │ ├── RedirectResponse #1387, #1451, #1464
+│ │ ├── Response #1456, #1472, #1477, #1486, #1504, #1505, #1497, #622
+│ │ ├── ResponseInterface #1384
+│ │ ├── UploadedFile #1368, #1456
+│ │ └── URI #1213, #1469, #1508
+│ ├── Images/Handlers/
+│ │ └── ImageMagickHandler #1546
+│ ├── Language/
+│ │ ├── en/Cache #1525
+│ │ ├── en/Database #1335
+│ │ ├── en/Filters #1378
+│ │ ├── en/Migrations #1374
+│ │ └── Language #1480, #1489
+│ ├── Log/
+│ │ └── Handlers/FileHandler #1547
+│ ├── Pager/
+│ │ ├── Pager #1213, #622
+│ │ ├── PagerInterface #622
+│ │ ├── PagerRenderer #1213, #622
+│ │ ├── Views/default_full #622
+│ │ ├── Views/default_head #622
+│ │ └── Views/default_simple #622
+│ ├── Router/
+│ │ ├── RouteCollection #1464, #1524
+│ │ ├── RouteCollectionInterface #1406, #1410
+│ │ └── Router #1523, #1547
+│ ├── Session/Handlers/
+│ │ ├── BaseHandler #1180, #1483
+│ │ ├── DatabaseHandler #1180
+│ │ ├── FileHandler #1180, #1547
+│ │ ├── MemcachedHandler #1180
+│ │ └── RedisHandler #1180
+│ ├── Test/
+│ │ ├── CIUnitTestCase #1467
+│ │ ├── FeatureTestCase #1427, #1468
+│ │ └── Filters/CITestStreamFilter #1465
+│ ├── Validation/
+│ │ ├── CreditCardRules #1447, #1529
+│ │ ├── FormatRules #1507
+│ │ ├── Rules #1345
+│ │ └── Validation #1345
+│ ├── View/
+│ │ ├── Filters #1469
+│ │ ├── Parser #1417, #1547
+│ │ └── View #1357, #1377, #1410, #1547
+│ ├── bootstrap #1547
+│ ├── CodeIgniter #1465, #1505, #1523, 2047b5a, #1547
+│ ├── Common #1486, #1496, #1504, #1513
+│ ├── ComposerScripts #1469, #1547
+│ ├── Controller #1423
+│ ├── Entity #1369, #1373
+│ └── Model #1345, #1380, #1373, #1440
+├── tests/
+│ ├── _support/
+│ │ ├── HTTP/MockResponse #1456
+│ │ └── _bootstrap.php #1397, #1443
+│ ├── Cache/Handlers/
+│ │ ├── FileHandlerTest #1547, #1525
+│ │ ├── MemcachedHandlerTest #1180, #1383
+│ │ └── RedisHandlerTest #1180, #1481
+│ ├── CLI/
+│ │ └── CLITest #1467, #1489
+│ ├── Commands/
+│ │ └── SessionCommandsTest #1455
+│ ├── Database/Live/
+│ │ ├── ConnectTest #1554
+│ │ └── ForgeTest #1449, #1470
+│ ├── HTTP/
+│ │ ├── CURLRequestTest#1498
+│ │ ├── Files/FileCollectionTest #1506
+│ │ ├── Files/FileMovingTest #1424
+│ │ ├── DownloadResponseTest #1375
+│ │ ├── IncomingRequestTest #1496
+│ │ ├── RedirectResponseTest #1387, #1456
+│ │ ├── ResponseCookieTest #1472, #1509
+│ │ ├── ResponseSendTest #1477, #1486, #1509
+│ │ ├── ResponseTest #1375, #1456, #1472, #1486, #622
+│ │ └── URITest #1456, #1495
+│ ├── Helpers/
+│ │ └── DateHelperTest #1479
+│ ├── I18n/
+│ │ └── TimeTest #1467, #1473
+│ ├── Language/
+│ │ └── LanguageTest #1480
+│ ├── Log/
+│ │ └── FileHandlerTest #1425
+│ ├── Pager/
+│ │ ├── PagerRendererTest #1213, #622
+│ │ └── PagerTest #622
+│ ├── Router/
+│ │ ├── RouteCollectionTest #1438, #1524
+│ │ └── RouterTest #1438, #1523
+│ ├── Session/
+│ │ └── SessionTest #1180
+│ ├── Test/
+│ │ ├── BootstrapFCPATHTest #1397
+│ │ ├── FeatureTestCase #1468
+│ │ ├── TestCaseEmissionsTest #1477
+│ │ └── TestCaseTest #1390
+│ ├── Throttle/
+│ │ └── ThrottleTest #1398
+│ ├── Validation/
+│ │ └── FormatRulesTest #1507
+│ ├── View/
+│ │ └── ParserTest #1335
+│ ├── CodeIgniterTest #1500
+│ ├── CommonFunctionsSendTest #1486, #1509
+│ └── CommonFunctionsTest #1180, #1486, #1496
+├── user_guide_src/source/
+│ ├── changelogs/ #1385, #1490, #1553
+│ ├── concepts/
+│ │ ├── autoloader #1547
+│ │ ├── security #1540
+│ │ ├── services #1469
+│ │ └── structure #1448
+│ ├── database/
+│ │ └── queries #1407
+│ ├── dbmgmt/
+│ │ ├── forge #1470
+│ │ ├── migration #1374, #1385, #1431
+│ │ └── seeds #1482
+│ ├── extending/
+│ │ └── core_classes #1469
+│ ├── helpers/
+│ │ └── form_helper #1499
+│ ├── installation/
+│ │ └── index #1388
+│ ├── libraries/
+│ │ ├── caching #1525
+│ │ ├── pagination #1213
+│ │ └── validation #27868b, #1540
+│ ├── models/
+│ │ └── entities #1518, #1540
+│ ├── outgoing/
+│ │ └── response #1472, #1494
+│ ├── testing/
+│ │ └── overview #1467
+│ └── tutorial/
+│ ├── create_news_item #1442
+│ └── static_pages #1547
+├── composer.json #1388, #1418, #1536, #1553
+├── README.md #1553
+├── spark 2047b5a
+└── .travis.yml #1394
+```
+
+## PRs merged
+
+- \#1554 Serviceinstances
+- \#1553 Admin/scripts
+- \#1550 remove commented CLI::newLine($tempFiles) at FileLocator
+- \#1549 use .gitkeep instead of .gitignore in Database/Seeds directory
+- \#1547 Change file exists to is file
+- \#1546 ImageMagickHandler::\__construct ...
+- \#1540 Update validation class User Guide
+- \#1530 database performance improvement: use foreach() when possible
+- 2047b5a Don't run filters when using spark.
+- \#1539 remove mb_\* (mb string usage) in CreditCardRules
+- \#1536 ext-json in composer.json
+- \#1525 remove unneeded try {} catch {}
+- \#1524 Test routes resource with 'websafe' option
+- \#1523 Check if the matched route regex is filtered
+- \#1522 add property_exists check on BaseBuilder
+- \#1521 .gitignore clean up
+- \#1518 Small typo: changed setCreatedOn to setCreatedAt
+- \#1517 move .htaccess from per-directory in writable/{directory} to writable/
+- \#1513 More secure redirection
+- \#1509 remove unused use statements
+- \#1508 remove duplicate strtolower() call in URI::setScheme() call
+- \#1507 Fix multi "empty" string separated by "," marked as valid emails
+- \#1506 Flesh out HTTP/File unit testing
+- \#1505 Do not exit until all Response is completed
+- 27868b Add missing docs for {field} and {param} placeholders
+- \#1504 Revert RedirectResponse changes
+- \#1500 Ignoring errors suppressed by @
+- \#1499 Fix form_helper's set_value writeup
+- \#1498 Add CURLRequest helper methods
+- \#1497 Remove unused RedirectException
+- \#1496 Fix Common::old()
+- \#1495 Add URI segment test
+- \#1494 Method naming in user guide
+- \#1491 Error logging
+- \#1490 Changelog(s) restructure
+- \#1489 Add CLI::strlen()
+- \#1488 Load Language strings from other locations
+- \#1486 Test RedirectResponse problem report
+- \#1484 missing slash
+- \#1483 Small typo in SessionHandlersBaseHandler.php
+- \#1482 doc fix: query binding fix in Seeds documentation
+- \#1481 RedisHandler test clean up
+- \#1480 Fix Language Key-File confusion
+- \#1479 Yet another time test to fix
+- \#1477 Add Response send testing
+- \#1475 Correct phpdocs for Forge::addField()
+- \#1473 Fuzzify another time test
+- \#1472 HTTPResponse cookie testing & missing functionality
+- \#1471 remove unused local variable $result in XMLFormatter::format()
+- \#1470 Allow create table with array field constraints
+- \#1469 use static:: instead of self:: to call protected/public functions as well
+- \#1468 Fix FeatureTestCaseTest output buffer
+- \#1467 Provide time testing within tolerance
+- \#1466 Fix phpdocs for BaseBuilder
+- \#1465 use static:: instead of self:: for protected and public properties
+- \#1464 remove unused use statements
+- \#1463 Fix the remaining bcit-ci references
+- \#1461 Typo fix: donload -> download
+- \#1460 remove unneeded ternary check at HoneyPot
+- \#1457 use $paths->systemDirectory in public/index.php
+- \#1456 Beef up HTTP URI & Response testing
+- \#1455 un-ignore app/Database/Migrations directory
+- \#1454 add missing break; in loop at Email::getEncoding()
+- \#1453 BugFix if the extension has only one mime type
+- \#1451 remove unneeded $session->start(); check on RedirectResponse
+- \#1450 phpcbf: fix all at once
+- \#1449 Simplify how to get indexData from mysql/mariadb
+- \#1448 documentation: add missing application structures
+- \#1447 add missing break; on loop cards to get card info at CreditCardRules
+- \#1445 using existing is_cli() function in HTTPIncomingRequest
+- \#1444 Dox for reorganized repo admin (4 of 4)
+- \#1443 Fixes unit test output not captured
+- \#1442 remove form view in app/View/ and form helper usage in create new items tutorial
+- \#1440 Access to model's last inserted ID
+- \#1438 Tailor the last few repo org names (3 of 4)
+- \#1437 Replace repo org name in MOST php docs (2 of 4)
+- \#1436 Change github organization name in docs (1 of 4)
+- \#1432 Use mb_strlen to get length of columns
+- \#1431 can't call run() method with params from commands migrations
+- \#1427 Fixes "options" request call parameter in FeatureTestCase
+- \#1416 performance improvement in DatabaseBaseResult
+- \#1425 Ensure FileHandlerTest uses MockFileHandler
+- \#1424 Fix FileMovingTest leaving cruft
+- \#1423 Fix Controller use validate bug
+- \#1422 fix Migrations.classNotFound
+- \#1418 normalize composer.json
+- \#1417 fix Parser::parsePairs always escapes
+- \#1416 remove $psr4['TestsSupport'] definition in applicationConfigAutoload
+- \#1415 remove unneeded "defined('BASEPATH') ...
+- \#1413 set more_entropy = true in all uniqid() usage
+- \#1412 function_exists() typo fixes on array_helper
+- \#1411 add missing break; in loop in View::render()
+- \#1410 Fix spark serve not working from commit 2d0b325
+- \#1407 Database: add missing call initialize() check on BaseConnection->prepare()
+- \#1406 Add missing parameter to RouteCollectionInterface
+- \#1405 Fix language string used in DownloadException
+- \#1402 Correct class namespacing in the user guide
+- \#1399 optional type hinting in guessExtension
+- \#1398 Tweak throttle testing
+- \#1397 Correcting FCPATH setting in tests/\_support/\_bootstrap.php
+- \#1396 only register PSR4 "TestsSupport" namespace in "testing" environment
+- \#1395 short array syntax in docs
+- \#1394 add php 7.3 to travis config
+- \#1390 Fixed not to output "Hello" at test execution
+- \#1389 Capitalize email filename
+- \#1388 Phpcs Auto-fix on commit
+- \#1387 Redirect to named route
+- \#1385 Fix migration page and update changelog
+- \#1384 add missing ResponseInterface constants
+- \#1383 fix TypeError in MemcachedHandler::\__construct()
+- \#1381 Remove unused use statements
+- \#1380 count() improvement, use truthy check
+- \#1378 Update Filters language file
+- \#1377 fix monolog will cause an error
+- \#1376 Fix cannot use class Honeypot because already in use in AppFiltersHoneypot
+- \#1375 Give download a header conforming to RFC 6266
+- \#1374 Missing feature migration.
+- \#1373 Turning off casting for db insert/save
+- \#1371 update method name in coding style
+- \#1370 Toolbar needs logging. Fixes #1258
+- \#1369 Remove invisible character
+- \#1368 UploadedFile->guessExtenstion()...
+- \#1360 rm --cached php_errors.log file
+- \#1357 Update template file is not .php compatibility
+- \#1345 is_unique tried to connect to default database instead of defined in DBGroup
+- \#1344 Do not quote unnecessary table options
+- \#1343 Avoid adding two single quotes to constraint
+- \#1335 Review and improvements in database drivers MySQLi, Postgre and SQLite
+- \#1213 URI segment as page number in Pagination
+- \#1180 using HTTPRequest instance to pull ip address
+- \#622 Add Header Link Pagination
diff --git a/docs/changelogs/v4.0.0-alpha.4.md b/docs/changelogs/v4.0.0-alpha.4.md
new file mode 100644
index 000000000000..3b0adb21b9e4
--- /dev/null
+++ b/docs/changelogs/v4.0.0-alpha.4.md
@@ -0,0 +1,211 @@
+# Version 4.0.0-alpha.4
+
+Release Date: Dec 15, 2018
+
+**Next alpha release of CodeIgniter4**
+
+## Highlights
+
+- Refactor for consistency: folder application renamed to **app/**; constant `BASEPATH` renamed to `SYSTEMPATH`
+- Debug toolbar gets its own config, history collector
+- Numerous corrections and enhancements
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── admin/
+│ ├── docbot #1573
+│ ├── framework/composer.json #1555
+│ ├── release #1573
+│ ├── release-deploy #1573
+│ └── starter/composer.json #1573, #1600
+├── app/
+│ ├── Config/
+│ │ ├── App #1571
+│ │ ├── Autoload #1579
+│ │ ├── ContentSecurityPolicy #1581
+│ │ ├── Events #1571, #1595
+│ │ ├── Paths #1579
+│ │ ├── Routes #1579
+│ │ ├── Services #1579
+│ │ └── Toolbar #1571, #1579
+│ ├── Filters/
+│ │ └── Toolbar #1571
+│ └── Views/
+│ └── errors/* #1579
+├── public/
+│ └── index #1579
+├── system/
+│ ├── Autoloader/
+│ │ ├── Autoloader #1562
+│ │ └── FileLocator #1562, #1579
+│ ├── CLI/
+│ │ └── CommandRunner #1562
+│ ├── Config/
+│ │ ├── AutoloadConfig #1555, #1579
+│ │ ├── BaseConfig #1562
+│ │ └── Services #1571, #1562
+│ ├── Database/
+│ │ ├── BaseBuilder #a0fc68
+│ │ ├── MigrationRunner #1585
+│ │ └── MySQLi/Connection #1561, #8f205a
+│ ├── Debug/
+│ │ ├── Collectors/* #1571, #1589, #1579
+│ │ ├── Exceptions #1579
+│ │ ├── Toolbar #1571
+│ │ ├── Views/toolbar.tpl #1571
+│ │ └── Views/toolbarloader.js #1594
+│ ├── Helpers/
+│ │ ├── form_helper #1548
+│ │ └── url_helper #1588
+│ ├── HTTP/
+│ │ ├── ContentSecurityPolicy #1581
+│ │ └── DownloadResponse
+│ ├── I18n/
+│ │ └── Time #1603
+│ ├── Language/
+│ │ ├── Language #1587, #1562, #1610
+│ │ └── en/
+│ │ ├── CLI #1562
+│ │ └── HTTP #d7dfc5
+│ ├── Log/
+│ │ ├── Handlers/FileHandler #1579
+│ │ └── Logger #1562, #1579
+│ ├── Session/
+│ │ └── Handlers/DatabaseHandler #1598
+│ ├── Test/
+│ │ ├── CIUnitTest #1581, #1593, #1579
+│ │ ├── FeatureResponse #1593
+│ │ └── FeatureTestCase #1593
+│ ├── View/
+│ │ └── View #1571, #1579
+│ ├── bootstrap #1579
+│ ├── CodeIgniter #ab8b5b, #1579
+│ ├── Common #1569, #1563, #1562, #1601, #1579
+│ ├── Entity #4c7bfe, #1575
+│ └── Model #1602, #a0fc68
+├── tests/
+│ ├── Autoloader/
+│ │ ├── AutoloaderTest #1562, #1579
+│ │ └── FileLocatorTest #1562, #1579
+│ ├── Config/
+│ │ └── ServicesTest #1562
+│ ├── Database/
+│ │ └── Live/ModelTest #1602, #a0fc68
+│ ├── Files/
+│ │ └── FileTest #1579
+│ ├── Helpers/
+│ │ ├── FormHelperTest #1548
+│ │ └── URLHelperTest #1588
+│ ├── HTTP/
+│ │ ├── ContentSecurityPolicyTest #1581
+│ │ ├── DownloadResponseTest #1576, #1579
+│ │ ├── IncomingRequestDetectingTest #1576
+│ │ ├── IncomingRequestTest #1576
+│ │ ├── RedirectResponseTest #1562
+│ │ └── ResponseTest #1576
+│ ├── I18n/
+│ │ ├── TimeDifferenceTest #1603
+│ │ └── TimeTest #1603
+│ ├── Language/
+│ │ └── LanguageTest #1587, #1610
+│ ├── Log/
+│ │ └── FileHandlerTest #1579
+│ ├── Router/
+│ │ ├── RouterCollectionTest #1562
+│ │ └── RouterTest #1562
+│ ├── Test/
+│ │ ├── FeatureResponseTest #1593
+│ │ ├── FeatureTestCaseTest #1593
+│ │ └── TestCaseTest #1593
+│ ├── Validation/
+│ │ └── ValidationTest #1562
+│ ├── View/
+│ │ ├── ParserPluginTest #1562
+│ │ ├── ParserTest #1562
+│ │ └── ViewTest #1562
+│ ├── CodeIgniterTest #1562
+│ ├── CommonFunctionsTest #1569, #1562
+│ └── EntityTest #4c7bfe, #1575
+├── user_guide_src/source/
+│ ├── cli/
+│ │ ├── cli #1579
+│ │ └── cli_commands #1579
+│ ├── concepts/
+│ │ ├── autoloader #1579
+│ │ ├── mvc #1579
+│ │ ├── services #1579
+│ │ └── structure #1579
+│ ├── database/
+│ │ └── configuration #1579
+│ ├── dbmgt/
+│ │ ├── migration #1579
+│ │ └── seeds #1579
+│ ├── general/
+│ │ ├── common_functions #d7dfc5, #1579
+│ │ ├── configuration #1608
+│ │ └── errors #1579
+│ ├── installation/
+│ │ └── downloads #1579
+│ ├── models/
+│ │ └── entities #547792, #1575
+│ ├── outgoing/
+│ │ ├── localization #1610
+│ │ ├── response #1581, #1579
+│ │ └── view_parser #1579
+│ ├── testing/
+│ │ ├── debugging #1579
+│ │ └── overview #1593, #1579
+│ └── tutorial/
+│ ├── news_section #1586
+│ └── static_pages #1579
+├── composer.json #1555
+├── ComposerScripts #1551
+├── spark #1579
+└── Vagrantfile.dist #1459
+```
+
+## PRs merged
+
+- \#1610 Test, fix & enhance Language
+- \#a0fc68 Clear binds after inserts, updates, and find queries
+- \#1608 Note about environment configuration in UG
+- \#1606 release framework script clean up
+- \#1603 Flesh out I18n testing
+- \#8f305a Catch mysql connection errors and sanitize username and password
+- \#1602 Model's first and update didn't work with primary key-less tables
+- \#1601 clean up ConfigServices in Common.php
+- \#1600 admin/starter/composer.json clean up
+- \#1598 use $defaultGroup as default value for database session DBGroup
+- \#1595 handle fatal error via pre_system
+- \#1594 Fix Toolbar invalid css
+- \#1593 Flesh out the Test package testing
+- \#1589 Fix Toolbar file loading throw exception
+- \#1588 Fix site_url generate invalid url
+- \#1587 Add Language fallback
+- \#1586 Fix model namespace in tutorial
+- \#1585 Type hint MigrationRunner methods
+- \#4c7bfe Entity fill() now respects mapped properties
+- \#547792 Add \_get and \_set notes for Entity class
+- \#1582 Fix changelog index & common functions UG indent
+- \#1581 ContentSecurityPolicy testing & enhancement
+- \#1579 Use Absolute Paths
+- \#1576 Testing13/http
+- \#1575 Adds ?integer, ?double, ?string, etc. cast types
+- \#ab8b5b Set baseURL to example.com during testing by default.
+- \#d7dfc5 Doc tweaks for redirects
+- \#1573 Lessons learned
+- \#1571 Toolbar updates
+- \#1569 Test esc() with different encodings and ignore app-only helpers
+- \#1563 id attribute support added for csrf_field
+- \#1562 Integrates Autoloader and FileLocator
+- \#1561 Update Connection.php
+- \#1557 remove prefix on use statements
+- \#1556 using protected instead of public modifier for setUp() function in tests
+- \#1555 autoload clean up: remove PsrLog namespace from composer.json
+- \#1551 remove manual define "system/" directory prefix at ComposerScripts
+- \#1548 allows setting empty html attr
+- \#1459 Add Vagrantfile
diff --git a/docs/changelogs/v4.0.0-alpha.5.md b/docs/changelogs/v4.0.0-alpha.5.md
new file mode 100644
index 000000000000..4e955cb182d2
--- /dev/null
+++ b/docs/changelogs/v4.0.0-alpha.5.md
@@ -0,0 +1,259 @@
+# Version 4.0.0-alpha.5
+
+Release Date: Jan 30, 2019
+
+**Next alpha release of CodeIgniter4**
+
+## Highlights
+
+- Added `$maxQueries` setting to **app/Config/Toolbar.php**
+- Updated PHP dependency to 7.2
+- New feature branches have been created for the email and queue modules, so they don't impact the release of 4.0.0
+- Dropped several language messages that were unused (e.g., `Migrations.missingTable`) and added some new ones (e.g., `Migrations.invalidType`)
+- Lots of bug fixes, especially for the database support
+- Provided filters (`CSRF`, `Honeypot`, `DebugToolbar`) have been moved from **app/Filters/** to **system/Filters/**
+- Revisited the installation and tutorial sections of the user guide
+- Code coverage is at 77% ... getting ever closer to our target of 80% :)
+
+We hope this will be the last alpha, and that the next pre-release will be our first beta ... fingers crossed!
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── admin/
+│ ├── starter/
+│ │ ├── README.md #1637
+│ │ └── app/Config/Paths.php #1685
+│ └── release-appstarter #1685
+├── app/
+│ └── Config/
+│ ├── Filters #1686
+│ ├── Modules #1665
+│ └── Services #614216
+│ └── Toolbar
+├── contributing/
+│ ├── guidelines.rst #1671, #1673
+│ └── internals.rst #1671
+├── public/
+│ └── index.php #1648, #1670
+├── system/
+│ ├── Autoloader/
+│ │ ├── Autoloader #1665, #1672
+│ │ └── FileLocator #1665
+│ ├── Commands/
+│ │ └── Database/MigrationRollback #1683
+│ ├── Config/
+│ │ ├── BaseConfig #1635
+│ │ ├── BaseService #1635, #1665
+│ │ ├── Paths #1626
+│ │ ├── Services #614216, #3a4ade, #1643
+│ │ └── View #1616
+│ ├── Database/
+│ │ ├── BaseBuilder #1640, #1663, #1677
+│ │ ├── BaseConnection #1677
+│ │ ├── Config #6b8b8b, #1660
+│ │ ├── MigrationRunner #81d371, #1660
+│ │ └── Query #1677
+│ ├── Database/Postgre/
+│ │ └── Builder #d2b377
+│ ├── Debug/Toolbar/Collectors/
+│ │ ├── Logs #1654
+│ │ └── Views #3a4ade
+│ ├── Events/
+│ │ └── Events #1635
+│ ├── Exceptions/
+│ │ └── ConfigException #1660
+│ ├── Files/
+│ │ ├── Exceptions/FileException #1636
+│ │ └── File #1636
+│ ├── Filters/
+│ │ ├── Filters #1635, #1625, #6dab8f
+│ │ ├── CSRF #1686
+│ │ ├── DebugToolbar #1686
+│ │ └── Honeypot #1686
+│ ├── Helpers/
+│ │ ├── form_helper #1633
+│ │ ├── html_helper #1538
+│ │ └── xml_helper #1641
+│ ├── HTTP/
+│ │ ├── ContentSecurityPolicy #1641, #1642
+│ │ └── URI #2e698a
+│ ├── Language/
+│ │ ├── /en/Files #1636
+│ │ └── Language #1641
+│ ├── Log/
+│ │ └── Handlers/FileHandler #1641
+│ ├── Router/
+│ │ ├── RouteCollection #1665, #5951c3
+│ │ └── Router #9e435c, #7993a7, #1678
+│ ├── Session/
+│ │ ├── Handlers/BaseHandler #1684
+│ │ ├── Handlers/FileHandler #1684
+│ │ ├── Handlers/MemcachedHandler #1679
+│ │ └── Session #1679
+│ ├── bootstrap #81d371, #1665
+│ ├── Common #1660
+│ ├── Entity #1623, #1622
+│ └── Model #1617, #1632, #1656, #1689
+├── tests/
+│ └── README.md #1671
+├── tests/system/
+│ ├── API/
+│ │ └── ResponseTraitTest #1635
+│ ├── Autoloader/
+│ │ ├── AutoloaderTest #1665
+│ │ └── FileLocatorTest #1665, #1686
+│ ├── CLI/
+│ │ ├── CommandRunnerTest #1635
+│ │ └── CommandsTest #1635
+│ ├── Config/
+│ │ ├── BaseConfigTest #1635
+│ │ ├── ConfigTest #1643
+│ │ └── ServicesTest #1635, #1643
+│ ├── Database/Builder/
+│ │ ├── AliasTest #bea1dd
+│ │ ├── DeleteTest #1677
+│ │ ├── GroupTest #1640
+│ │ ├── InsertTest #1640, #1677
+│ │ ├── LikeTest #1640, #1677
+│ │ ├── SelectTest #1663
+│ │ ├── UpdateTest #1640, #1677
+│ │ └── WhereTest #1640, #1677
+│ ├── Database/Live/
+│ │ ├── AliasTest #1675
+│ │ ├── ConnectTest #1660, #1675
+│ │ ├── ForgeTest #6b8b8b
+│ │ ├── InsertTest #1677
+│ │ ├── Migrations/MigrationRunnerTest #1660, #1675
+│ │ └── ModelTest #1617, #1689
+│ ├── Events/
+│ │ └── EventTest #1635
+│ ├── Filters/
+│ │ ├── CSRFTest #1686
+│ │ ├── DebugToolbarTest #1686
+│ │ ├── FiltersTest #1635, #6dab8f, #1686
+│ │ └── HoneypotTest #1686
+│ ├── Helpers/
+│ │ ├── FormHelperTest #1633
+│ │ └── XMLHelperTest #1641
+│ ├── Honeypot/
+│ │ └── HoneypotTest #1686
+│ ├── HTTP/
+│ │ ├── ContentSecurityPolicyTest #1641
+│ │ └── IncomingRequestTest #1641
+│ ├── Language/
+│ │ └── LanguageTest #1643
+│ ├── Router/
+│ │ ├── RouteCollectionTest #5951c3
+│ │ └── RouterTest #9e435c
+│ ├── Validation/
+│ │ └── RulesTest #1689
+│ └── View/
+│ ├── ParserPluginTest #1669
+│ └── ParserTest #1669
+├── user_guide_src/
+│ ├── concepts/
+│ │ ├── autoloader #1665
+│ │ └── structure #1648
+│ ├── database/
+│ │ ├── connecting #1660
+│ │ └── transactions #1645
+│ ├── general/
+│ │ ├── configuration #1643
+│ │ ├── managing_apps #5f305a, #1648
+│ │ └── modules #1613, #1665
+│ ├── helpers/
+│ │ └── form_helper #1633
+│ ├── incoming/
+│ │ ├── filters #1686
+│ │ ├── index #4a1886
+│ │ └── methodspoofing #4a1886
+│ ├── installation/
+│ │ ├── index #1690, #1693
+│ │ ├── installing_composer #1673, #1690
+│ │ ├── installing_git #1673, #1690
+│ │ ├── installing_manual #1673, #1690
+│ │ ├── repositories #1673, #1690
+│ │ ├── running #1690, #1691
+│ │ └── troubleshooting #1690, #1693
+│ ├── libraries/
+│ │ ├── honeypot #1686
+│ │ ├── index #1643, #1690
+│ │ └── throttler #1686
+│ └── tutorial/
+│ ├── create_news_item #1693
+│ ├── index #1693
+│ ├── news_section #1693
+│ └── static_pages #1693
+├── composer.json #1670
+├── contributing.md #1670
+├── README.md #1670
+├── spark #1648
+└── .travis.yml #1649, #1670
+```
+
+## PRs merged
+
+- \#1693 Docs/tutorial
+- \#5951c3 Allow domain/sub-domain routes to overwrite existing routes
+- \#1691 Update the running docs
+- \#1690 Rework install docs
+- \#bea1dd Additional AliasTests for potential LeftJoin issue
+- \#1689 Model Validation Fix
+- \#1687 Add copyright blocks to filters
+- \#1686 Refactor/filters
+- \#1685 Fix admin - app starter creation
+- \#1684 Updating session id cleanup for filehandler
+- \#1683 Fix migrate:refresh bug
+- \#d2b377 Fix Postgres replace command to work with the new way of storing binds
+- \#4a1886 Document method spoofing
+- \#2e698a urldecode URI keys as well as values.
+- \#1679 save_path - for memcached
+- \#1678 fix route not replacing forward slashes
+- \#1677 Implement Don't Escape feature for db engine
+- \#1675 Add missing test group directives
+- \#1674 Update changelog
+- \#1673 Updated download & installation docs
+- \#1672 Update Autoloader.php
+- \#1670 Update PHP dependency to 7.2
+- \#1671 Update docs
+- \#1669 Enhance Parser & Plugin testing
+- \#1665 Composer PSR4 namespaces are now part of the modules auto-discovery
+- \#6dab8f Filters match case-insensitively
+- \#1663 Fix bind issue that occurred when using whereIn
+- \#1660 Migrations Tests and database tweaks
+- \#1656 DBGroup in \__get() allows validating "database" data outside the model
+- \#1654 Toolbar - Return Logger::$logCache items
+- \#1649 remove php 7.3 from "allow_failures" in travis config
+- \#1648 Update "managing apps" docs
+- \#1645 Fix confusion about enabling transactions (docu)
+- \#1643 Remove email module
+- \#1642 CSP nonce attribute value in ""
+- \#81d371 Safety checks for config files during autoload and migrations
+- \#1641 More unit testing tweaks
+- \#1640 Update getCompiledX methods in BaseBuilder
+- \#1637 Fix starter README
+- \#1636 Refactor Files module
+- \#5f305a UG - Typo in managing apps
+- \#1635 Unit testing enhancements
+- \#1633 Uses csrf_field and form_hidden
+- \#1632 DBGroup should be passed to ->run instead of ->setRules
+- \#1631 move use statement after License doc at UploadedFile class
+- \#1630 Update copyright to 2019
+- \#1629 "application" to "app" directory doc and comments
+- \#3a4ade view() now properly reads the app config again
+- \#7993a7 Final piece to get translateURIDashes working appropriately
+- \#9e435c TranslateURIDashes fix
+- \#1626 clean up Paths::$viewDirectory property
+- \#1625 After matches is not set empty
+- \#1623 Property was not cast if it was defined as nullable
+- \#1622 Nullable support for \__set
+- \#1617 countAllResults() should respect soft deletes
+- \#1616 Fix View config merge order
+- \#614216 Moved honeypot service out of the app Services file to the system Services where it belongs
+- \#6b8b8b Allow db forge and utils to take an array of connection info instead of a group name
+- \#1613 Typo in documentation
+- \#1538 img fix(?) - html_helper
diff --git a/docs/changelogs/v4.0.0-beta.1.md b/docs/changelogs/v4.0.0-beta.1.md
new file mode 100644
index 000000000000..41947c0b37cf
--- /dev/null
+++ b/docs/changelogs/v4.0.0-beta.1.md
@@ -0,0 +1,178 @@
+# Version 4.0.0-beta.1
+
+Release Date: Mar 1, 2019
+
+## Highlights
+
+- New View Layouts provide a simple way to create site view templates.
+- Fixed user guide CSS for proper wide table display
+- Converted UploadedFile to use system messages
+- Numerous database, migration & model bugs fixed
+- Refactored unit testing for appstarter & framework distributions
+
+## New Messages
+
+- `Database.tableNotFound`
+- `HTTP.uploadErr...`
+
+## App Changes
+
+- **app/Config/Cache** has a new setting: database
+- **app/Views/welcome_message** has logo tinted
+- **composer.json** has a case correction
+- **.env** adds `CI_ENVIRONMENT` suggestion
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── app/
+│ ├── Config/
+│ │ └── Cache #1719
+│ └── Views/
+│ └── welcome_message #1774
+├── system/
+│ ├── Cache/Handlers/
+│ │ └── RedisHandler #1719, #1723
+│ ├── Config/
+│ │ ├── Config #37dbc1
+│ │ └── Services #1704, #37dbc1
+│ ├── Database/
+│ │ ├── Exceptions/DatabaseException #1739
+│ │ ├── Postgre/
+│ │ │ └── Builder #1733
+│ │ ├── SQLite3/
+│ │ │ ├── Connection #1739
+│ │ │ ├── Forge #1739
+│ │ │ └── Table #1739
+│ │ ├── BaseBuilder #36fbb8, #549d7d
+│ │ ├── BaseConnection #549d7d, #1739
+│ │ ├── Forge #1739
+│ │ ├── MigrationRunner #1743
+│ │ ├── Query #36fbb8
+│ │ └── Seeder #1722
+│ ├── Debug/
+│ │ └── Exceptions #1704
+│ ├── Files/
+│ │ └── UploadedFile #1708
+│ ├── Helpers/
+│ │ ├── date_helper #1768
+│ │ ├── number_helper #1768
+│ │ ├── security_helper #1768
+│ │ ├── text_helper #1768
+│ │ └── url_helper #1768
+│ ├── HTTP/
+│ │ └── Request #1725
+│ ├── Language/en/
+│ │ ├── Database #1739
+│ │ ├── HTTP #1708
+│ │ └── View #1757
+│ ├── Router/
+│ │ ├── RouteCollection #1709, #1732
+│ │ └── Router #1764
+│ ├── Test/
+│ │ ├── ControllerResponse #1740
+│ │ ├── ControllerTester #1740
+│ │ ├── DOMParser #1740
+│ │ └── FeatureResponse #1740
+│ ├── Validation/
+│ │ ├── Rules #1738, #1743
+│ │ └── Validation #37dbc1, #1763
+│ ├── View/
+│ │ └── View #1729
+│ ├── Common #1741
+│ ├── Entity #6e549a, #1739
+│ └── Model #4f4a37, #6e549a, #37dbc1, #1712, #1763
+├── tests/system/
+│ ├── Database/
+│ │ ├── BaseQueryTest #36fbb8
+│ │ ├── Live/
+│ │ │ ├── SQLite3/AlterTableTest #1739, #1740
+│ │ │ ├── ForgeTest #1739, #1745
+│ │ │ └── ModelTest #37dbc1, #4ff1f5, #1763
+│ │ └── Migrations/MigrationRunnerTest #1743
+│ ├── Helpers/
+│ │ └── FilesystemHelperTest #1740
+│ ├── I18n/
+│ │ └── TimeTest # 1736
+│ ├── Test/
+│ │ └── DOMParserTest #1740
+│ ├── Validation/
+│ │ └── ValidationTest #1763
+│ ├── View/
+│ │ └── ViewTest #1729
+│ └── EntityTest #6e549a, #1736
+├── user_guide_src/
+│ ├── _themes/.../
+│ │ └── citheme.css #1696
+│ ├── changelogs/
+│ │ └── v4.0.0-alpha.5 #1699
+│ ├── database/
+│ │ └── migrate #1696
+│ ├── dbmgmt/
+│ │ └── forge #1751
+│ ├── installation/
+│ │ ├── install_manual #1699
+│ │ └── running #1750
+│ ├── intro/
+│ │ └── psr #1752
+│ ├── libraries/
+│ │ ├── caching #1719
+│ │ └── validation #1742
+│ ├── models/
+│ │ └── entities #1744
+│ ├── outgoing/
+│ │ ├── index #1729
+│ │ └── view_layouts #1729
+│ ├── testing/
+│ │ └── controllers #1740
+│ └── tutorial/
+│ └── static_pages #1763
+├── composer.json #1755
+└── .env #1749
+```
+
+## PRs merged
+
+- \#1774 Housekeeping for beta.1
+- \#1768 Helper changes - signatures & typos
+- \#1764 Fix routing when no default route has been specified. Fixes #1758
+- \#1763 Ensure validation works in Model with errors as part of rules. Fixes #1574
+- \#1757 Correct the unneeded double-quote (typo)
+- \#1755 lowercase 'vfsStream' in composer files
+- \#1752 Fixed typo preventing link format
+- \#1751 Guide: Moving misplaced text under correct heading
+- \#1750 Remove reference to Encryption Key in User Guide
+- \#1749 Adding environment to .env
+- \#1745 Updated composite key tests for SQLite3 support. Fixes #1478
+- \#1744 Update entity docs for current framework state. Fixes #1727
+- \#1743 Manually sort migrations found instead of relying on the OS. Fixes #1666
+- \#1742 Fix required_without rule bug.
+- \#1741 Helpers with a specific namespace can be loaded now. Fixes #1726
+- \#1740 Refactor test support for app starter
+- \#1739 Fix typo
+- \#1738 Fix required_with rule bug. Fixes #1728
+- \#1737 Added support for dropTable and modifyTable with SQLite driver
+- \#1736 Accommodate long travis execution times
+- \#1733 Fix increment and decrement errors with Postgres
+- \#1732 Don't check from CLI in Routes. Fixes #1724
+- \#1729 New View Layout functionality for simple template
+- \#1725 Update Request.php
+- \#1723 Log an error if redis authentication fails
+- \#1722 Seeder adds default namespace to seeds
+- \#1719 Update Cache RedisHandler to support selecting a database
+- \#4ff1f5 Additional tests for inserts and required validation failing (#1717)
+- \#549d7d Another try at getting escaping working correctly both when in and out of models
+- \#1712 Minor readability changes
+- \#37dbc1 Ensure Model validation rules can be a group name
+- \#1709 Fix resource routing websafe method order checking
+- \#1708 Language for UploadedFile
+- \#36fbb8 BaseBuilder should only turn off Connection's setEscapeFlags when running a query...
+- \#6e549a Provide default baseURL that works with the development server for easier first time setup (Fixes #1646)
+- \#1704 Fix viewsDirectory bug (#1701)
+- \#4f4a37 remove debugging from Model.
+- \#1699 Fix install link in user guide
+- \#1696 Fix page structure etc
+- \#1695 Tidy up code blocks in the user guide
diff --git a/docs/changelogs/v4.0.0-beta.2.md b/docs/changelogs/v4.0.0-beta.2.md
new file mode 100644
index 000000000000..3bd8fc35d889
--- /dev/null
+++ b/docs/changelogs/v4.0.0-beta.2.md
@@ -0,0 +1,265 @@
+# Version 4.0.0-beta.2
+
+Release Date: April 4, 2019
+
+## Highlights
+
+- A number of fixes & improvements, importantly for the `Model` and testing classes
+- Models now require a primary key
+- Generated API docs accessible at
+- Validation rules have been enhanced
+- **.htaccess** beefed up
+
+## New messages
+
+- `Database.noPrimaryKey`
+- `Database.forFindColumnHaveMultipleColumns`
+- `Database.forEmptyInputGiven`
+
+## App Changes
+
+- Updated **app/Config/Events**
+- Added **app/Controllers/BaseController**
+- Added **tests/** folder for unit testing
+- Added **phpunit.xml.dist** for unit testing configuration
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── .htaccess #1900
+├── app/
+│ ├── Config/
+│ │ └── Events #1856
+│ └── Controllers/
+│ ├── BaseController #1847
+│ └── Home #1847
+├── contributing/
+│ ├── README.rst #1846
+│ └── styleguide #1872
+├── contributing.md #1846
+├── phpdoc.dist.xml #1872
+├── system/
+│ ├── Autoloader/
+│ │ └── FileLocator #1860
+│ ├── Cache/Handlers/
+│ │ ├── FileHandler #1895
+│ │ ├── MemcachedHandler #1895
+│ │ ├── PredisHandler #1895
+│ │ ├── RedisHandler #1863, #1895
+│ │ └── WincacheHandler #1895
+│ ├── CLI/
+│ │ └── CLI #1891, #1910
+│ ├── Commands/
+│ │ ├── Server/Serve #1893
+│ │ └── Utilities/Routes #1859
+│ ├── Config/
+│ │ ├── BaseConfig #1811
+│ │ └── Routes #1847, #1850
+│ ├── Database/
+│ │ ├── BaseBuilder $1776, #1902
+│ │ ├── BaseConnection #1899
+│ │ ├── Forge #1844, #1899
+│ │ ├── MigrationRunner #1860, #1865
+│ │ ├── MySQLi/Connection #1896
+│ │ ├── MySQLi/Forge #1899
+│ │ ├── Postgre/Builder #1902
+│ │ ├── Postgre/Forge #1899
+│ │ ├── Query #1805, #1771
+│ │ ├── SQLite3/Builder #1902
+│ │ └── SQLite3/Forge #1899
+│ ├── Debug/
+│ │ ├── Toolbar/Collectors/History #1869
+│ │ └── Toolbar #1897
+│ ├── Events/
+│ │ └── Events #1867
+│ ├── Exceptions/
+│ │ ├── ModelException #1829
+│ │ └── PageNotFoundException #1844
+│ ├── Files/
+│ │ └── File #1809, #1854
+│ ├── Helpers/
+│ │ ├── date_helper #d08b68
+│ │ ├── form_helper #1803
+│ │ ├── html_helper #1803
+│ │ ├── number_helper #d08b68, #1803
+│ │ ├── security_helper #d08b68
+│ │ ├── text_helper #d08b68, #1803
+│ │ ├── url_helper #d08b68, #1803
+│ │ └── xml_helper #1803
+│ ├── Honeypot/
+│ │ └── Honeypot #1894
+│ ├── HTTP/
+│ │ ├── Header #1769
+│ │ └── IncomingRequest #1831
+│ ├── Language/en/
+│ │ └── Database #1829, #1861, #1902
+│ ├── Router/
+│ │ ├── RouteCollection #1769
+│ │ └── Router #1839, #1882
+│ ├── Session/
+│ │ └── Session #1769
+│ ├── Test/
+│ │ ├── ControllerTester #1769, #1848, #1855
+│ │ └── DOMParser #1848
+│ ├── Validation/
+│ │ ├── FormatRules #1762, #1863
+│ │ ├── Rules #1791, #1814, #1818, #1862
+│ │ ├── Validation #1769
+│ │ └── Views/list #1828
+│ ├── View/
+│ │ ├── Filters #1769
+│ │ ├── Parser #1769
+│ │ └── View #1769, #1827
+│ ├── CodeIgniter #1769, #1804, #1590
+│ ├── Common #1802, #895ae0
+│ ├── ComposerScripts #1804
+│ ├── Controller #1769, #1850
+│ ├── Entity #1769, #1804
+│ └── Model #1793, #1769, #1804, #1808, #1812, #1813, #1817, #1829, #1746, #1861
+├── tests/system/
+│ ├── Cache/
+│ │ ├── Handlers/
+│ │ │ ├── FileHandlerTest #1796, #1895
+│ │ │ ├── MemcachedHandlerTest #1895
+│ │ │ └── RedisHandlerTest #1895
+│ │ └── CacheFactoryTest #1796
+│ ├── CLI/
+│ │ └── CLITest #1910
+│ ├── Config/
+│ │ ├── BaseConfigTest #1811
+│ │ └── ConfigTest #1811
+│ ├── Database/
+│ │ ├── Builder/EmptyTest #1902
+│ │ ├── Builder/SelectTest #1902
+│ │ ├── Live/ModelTest #1817, #1829, #1861
+│ │ └── Live/WhereTest #1906
+│ ├── Events/
+│ │ └── EventsTest #1867
+│ ├── HTTP/
+│ │ └── ContentSecurityPolicyTest #1848
+│ ├── Router/
+│ │ └── RouteCollectionTest #1822, #1912, #1913
+│ ├── Test/
+│ │ ├── ControllerTesterTest #1848, #1855
+│ │ └── DOMParserTest #1848
+│ ├── Validation/
+│ │ ├── FormatRulesTest #1762
+│ │ └── RulesTest #1791
+│ ├── View/
+│ │ └── ViewTest #1827, #1836
+│ └── ControllerTest #1850
+└── user_guide_src/
+ ├── cli/
+ │ ├── cli_commands #1777
+ │ └── cli_library #1892, #1910
+ ├── concepts/
+ │ └── services #1811
+ ├── database/
+ │ └── examples #1794
+ ├── dbmgmt/
+ │ ├── forge #1844, #1899
+ │ └── migration #1860, #1865
+ ├── extending/
+ │ ├── basecontroller #1847
+ │ └── core_classes #1847
+ ├── general/
+ │ └── common_functions #1802, #1895
+ ├── helpers/
+ │ ├── number_helper #d08b68
+ │ └── url_helper #1803
+ ├── incoming/
+ │ └── routing #1908
+ ├── libraries/
+ │ ├── caching #1895
+ │ ├── files #1790, #1854
+ │ ├── pagination #1823
+ │ ├── sessions #1843
+ │ └── validation #1814, #1828, #1862
+ ├── models/
+ │ └── models #1817, #1820, #1829, #1746, #1861
+ ├── outgoing/
+ │ └── view_layouts #1827
+ └── testing/
+ └── controllers #1848
+```
+
+## PRs merged
+
+- \#1913 More RouteCollection tests for overwriting. Closes #1692
+- \#1912 Additional RouteCollectionTests
+- \#1910 Added print method to CLI library so you can print multiple times on same line
+- \#1908 Add filter parameters to User Guide
+- \#1906 SubQuery related test cases w.r.t #1775
+- \#1902 BaseBuilder corrections
+- \#1900 Update .htaccess for better security and caching
+- \#1899 Database Forge correction
+- \#1897 Toolbar fix w.r.t #1779
+- \#1896 Mysql connection issue with SSL cert (#1219)
+- \#1894 Typo fixes
+- \#1893 Fix spark serve with remove escapeshellarg()
+- \#1892 Add CLI background color list to the user guide
+- \#1891 Allow CLI::strlen null parameter
+- \#1886 Fixed issue #1880, fixed a few typos and updated code style
+- \#1882 Router Changes w.r.t #1541
+- \#1873-1889 Docs: move namespace declarations & add missing class docblocks
+- \#1872 Docs: fix phpdoc config
+- \#1871 Unmatched Cache Library `get()` return null
+- \#1869 History::SetFiles check #1778
+- \#1863 Module wise Typos changes
+- \#1861 New method Find Column w.r.t. #1619
+- \#1860 Migrationrunner use autoloader
+- \#1867 Events should actually work with any callable now. Fixes #1835
+- \#1865 MigrationRunner issue with definition resolved
+- \#1862 required_with and required_without definition changes
+- \#1859 Ignore callbacks in routes list
+- \#1858 Typos correction in DB module
+- \#1856 ensure ob_end_flush() when ob_get_level() \> 0 on pre_system event
+- \#1855 Fix: ControllerTester::execute. Fixes #1834
+- \#1854 File::move now returns new file instance for relocated file. Fixes #1782
+- \#1851 Replace old CI3 .gitignore with root CI4 version
+- \#1850 Secure routable controller methods
+- \#1848 Test: fix & test Test/ControllerTest, tested
+- \#1847 Extend Controller to BaseController by default
+- \#1846 Fix contributing links
+- \#1844 Model Fix
+- \#1843 Replace CI3 $this->input reference
+- \#1842 Exception 'forPageNotFound' missing default value
+- \#1839 Don't replace slashes with backslashes in the to route
+- \#1836 Test: Improve ViewView coverage
+- \#1831 Fix some PHPDoc comment errors
+- \#1829 Improve: Models now require a primary key. This is partially to keep the code ...
+- \#1828 Fix: Remove bootstrap styles from validation views.
+- \#1827 Fix: Adding include method to View library to render view partials... views.
+- \#1823 Docs: Remove legacy Bootstrap references in Pagination class
+- \#1822 Test: enhance RouteCollection coverage
+- \#1820 Fix: Correct sphinx errors in model.rst
+- \#1819 Improve: Add apibot for API docs using phpDocumentor
+- \#1818 Improve: Code improvement in exact_length Rule
+- \#1817 Improve: Model setValidationMessage functions introduced
+- \#895ae0 Fix: Start session whenever using the old command
+- \#1814 Enhance: extended exact_length[1,3,5]
+- \#1813 Fix: Model::save fix for earlier PRs
+- \#1812 Test: Improve Filters coverage
+- \#1811 Test: Config module coverage improved
+- \#1809 Fix failed file move. Fixes #1785
+- \#1808 Fix: Fix save method return value
+- \#1805 Docs: Query Class Changes
+- \#1804 Docs: Some Base Functional Changes
+- \#1803 Docs: Some Helper Changes
+- \#1802 Docs: Common function correction
+- \#1796 Test: Improve Cache coverage
+- \#1794 Replace nonexistent "getAffectedRows"
+- \#1793 Set Model->chunk return type
+- \#1791 Fix: Remove is_numeric tests in ValidationRules
+- \#d08b68 Fix in ControllerTester for missing UserAgent
+- \#1790 Correction of typos in documentation as mentioned in issue #1781
+- \#1777 Add CLI namespace to example
+- \#1776 Fix: replace only last operator in field name
+- \#1771 Fix: fix typo in matchSimpleBinds
+- \#1769 Correction in Methods and Spellings
+- \#1762 Fix: decimal rule. shouldn't it accept integers?
+- \#1746 Improve: Update Model, to selective update created_at / updated_at field.
+- \#1590 Improve: Enhance 404Override
diff --git a/docs/changelogs/v4.0.0-beta.3.md b/docs/changelogs/v4.0.0-beta.3.md
new file mode 100644
index 000000000000..25f3ebc5fd63
--- /dev/null
+++ b/docs/changelogs/v4.0.0-beta.3.md
@@ -0,0 +1,183 @@
+# Version 4.0.0-beta.3
+
+Release Date: May 7, 2019
+
+## Highlights
+
+- Type hinting added throughout & typos corrected (see API docs)
+- Fixed a number of model, database, validation & debug toolbar issues
+
+## New Messages
+
+- `Database.fieldNotExists`
+- `Validation.equals`
+- `Validation.not_equals`
+
+## App Changes
+
+- Removed `$salt` config item in **app/Config/App**
+- Enabled migrations by default in **app/Config/Migrations**
+- Simplified **public/.htaccess**
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── admin/
+│ ├── framework/composer.json #1935
+│ └── starter/composer.json #1935
+├── app/
+│ └── Config/
+│ ├── App #1973
+│ └── Migrations #1973
+├── public/
+│ └── .htaccess #1973
+├── system/
+│ ├── API/
+│ │ └── ResponseTrait #1962
+│ ├── Commands/
+│ │ └── Server/rewrite #1925
+│ ├── Config/
+│ │ ├── AutoloadConfig #1974
+│ │ └── BaseConfig #1947
+│ ├── Database/ #1938
+│ │ ├── BaseBuilder #1923, #1933, #1950
+│ │ ├── BaseConnection #1950
+│ │ ├── BaseResult #1917
+│ │ ├── BaseUtils #1917
+│ │ ├── Forge #1917
+│ │ └── SQLite3/
+│ │ ├── Connection #1917
+│ │ └── Result #1917
+│ ├── Debug/
+│ │ ├── Toolbar #1916
+│ │ ├── Toolbar/Collectors/
+│ │ │ ├── BaseCollector #1972
+│ │ │ ├── Config #1973
+│ │ │ ├── History #1945
+│ │ │ └── Routes #1949
+│ │ └── Toolbar/Views/
+│ │ ├── _config.tpl.php #1973
+│ │ ├── toolbar.tpl.php #1972
+│ │ └── toolbarloader.js #1931, #1961
+│ ├── Exceptions/
+│ │ └── EntityException #1927
+│ ├── Filters/
+│ │ └── Filters #1970, #1985
+│ ├── Format/
+│ │ ├── FormatterInterface #1918
+│ │ ├── JSONFormatter #1918
+│ │ └── XMLFormatter #1918
+│ ├── HTTP/
+│ │ ├── CLIRequest #1956
+│ │ └── CURLRequest #1915
+│ ├── Images/Handlers/
+│ │ └── BaseHandler #1956
+│ ├── Language/en/
+│ │ ├── Database #1917
+│ │ └── Validation #1952
+│ ├── Router/
+│ │ ├── Router #1968
+│ │ └── RouteCollection #1977
+│ ├── Session/Handlers/
+│ │ └── RedisHandler #1980
+│ ├── Test/
+│ │ ├── FeatureResponse #1977
+│ │ └── FeatureTestCase #1977
+│ ├── Validation/
+│ │ ├── FormatRules #1957
+│ │ └── Rules #1952
+│ ├── View/
+│ │ └── Table #1984
+│ ├── Entity #1911, #1927, #1943, #1950, #1955
+│ └── Model #1930, #1943, #1963, #1981
+├── tests/system/
+│ ├── Config/
+│ │ └── BaseConfigTest #1947
+│ ├── Database/
+│ │ ├── BaseQueryTest #1917
+│ │ ├── Live/
+│ │ │ ├── DbUtilsTest #1917, #1943
+│ │ │ ├── ForgeTest #1917
+│ │ │ ├── GetTest #1917, #1943
+│ │ │ └── ModelTest #1930, #1943, #1981
+│ │ └── Migrations/
+│ │ ├── MigrationRunnerTest #1917
+│ │ └── MigrationTest #1943
+│ ├── Filters/
+│ │ └── FilterTest #1985
+│ ├── Test/
+│ │ └── FeatureTestCaseTest #1977
+│ ├── Validation/
+│ │ ├── FormatRulesTest #1957
+│ │ └── RulesTest #1952, #cbe4b1d
+│ ├── View/
+│ │ └── TableTest #1978, #1984
+│ └── EntityTest #1911
+├── user_guide_src/
+│ ├── dbmgmt/
+│ │ └── migrations #1973
+│ ├── installation/
+│ │ ├── installing_composer #1926
+│ │ └── running #1935
+│ ├── libraries/
+│ │ └── validation #1952, #1954, #1957
+│ ├── outgoing/
+│ │ ├── index #1978
+│ │ └── table #1978, #1984
+│ └── testing/
+│ ├── feature #1977
+│ └── overview #1936
+├── .htaccess #1939
+├── composer.json #1935
+└── phpdoc.dist.xml #1987
+```
+
+## PRs merged
+
+- \#1987 Correct API docblock problems for phpdocs
+- \#1986 Update docblock version to 4.0.0
+- \#1985 Fix filter processing. Fixes #1907
+- \#cbe4b1d Fix SQLite tests
+- \#1984 Add footing to HTML Table
+- \#1981 Using soft deletes should not return an ambiguous field message when joining tables
+- \#1980 Corrected return value for Session/RedisHandler::read
+- \#1978 Implement HTML Table for CI4 (missed feature)
+- \#1977 Test/featuretestcase
+- \#1974 Remove framework classes from the autoloader classmap
+- \#1973 Defaultfixes
+- \#1972 Toolbar fix for custom collectors
+- \#1970 Add back filter arguments
+- \#1968 Fixed pathinfo mode 404 error
+- \#1963 String type primary key should also wrap into an array during db update
+- \#1962 Fix side issue
+- \#1961 Fix Debugbar url tail slash issue
+- \#1957 New generic string validation rule
+- \#1956 Use Null Coalesce Operator
+- \#1955 Travis-CI build failed fix
+- \#1954 Fix validation table format
+- \#1952 Add Validations for `equals()` and `not_equals()`
+- \#1951 System typos changes & code cleanup
+- \#1950 Fix some side issues
+- \#1949 Toolbar/Routes correction
+- \#1947 Fix BaseConfig didn't load Registrar files properly
+- \#1945 Fix datetime extraction from debugbar file
+- \#1943 Model, Entity, Exception & Migration test cases
+- \#1939 Remove section that prevents hotlinking
+- \#1938 Database typos changes
+- \#1936 Docs: improve app testing writeup
+- \#1935 Update phpunit.xml scripts. Fixes #1932
+- \#1933 having (Is NULL deletion)
+- \#1931 Toolbar IE11 fix
+- \#1930 Model Changes w.r.t. #1773
+- \#1927 Entity exception for nonexistent props
+- \#1926 Docs: update installation guide
+- \#1925 removed $_SERVER['CI_ENVIRONMENT']
+- \#1923 missing return
+- \#1918 JSONFormatter
+- \#1917 Database Test Cases
+- \#1916 Check if the value is a string
+- \#1915 Fix for POST + JSON (Content-Length added)
+- \#1911 JSON Cast exception test cases
diff --git a/docs/changelogs/v4.0.0-beta.4.md b/docs/changelogs/v4.0.0-beta.4.md
new file mode 100644
index 000000000000..7664631aaa67
--- /dev/null
+++ b/docs/changelogs/v4.0.0-beta.4.md
@@ -0,0 +1,192 @@
+# Version 4.0.0-beta.4
+
+Release Date: Jul 25, 2019
+
+## Highlights
+
+There are some breaking changes...
+
+- The Entity class has been refactored
+- The Model class has been updated to better handle soft deletes
+- The routing has been beefed up
+
+## New Messages
+
+- New translation key: `Database.noDateFormat`
+
+## Testing Changes
+
+- Enhanced database & migration testing in **tests/\_support/**
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── admin/
+├── app/
+│ └── Controllers/
+│ └── Home #1999
+├── public/
+├── system/
+│ ├── Autoloader/
+│ │ └── FileLocator #2059, #2064
+│ ├── Cache/
+│ │ ├── CacheFactory #2060
+│ │ └── Handlers/
+│ │ ├── MemcachedHandler #2060
+│ │ ├── PredisHandler #2060
+│ │ └── RedisHandler #2060
+│ ├── Commands/
+│ │ └── Utilities/Routes #2008
+│ ├── Config/
+│ │ ├── Config #2079
+│ │ └── Services #2024
+│ ├── Database/
+│ │ ├── MySQLi/
+│ │ │ ├── Connection #2042
+│ │ │ └── Result #2011
+│ │ ├── Postgre/
+│ │ │ ├── Connection #2042
+│ │ │ └── Result #2011
+│ │ ├── SQLite3/
+│ │ │ ├── Connection #2042
+│ │ │ ├── Forge #2042
+│ │ │ ├── Result #2011
+│ │ │ └── Table #2042
+│ │ ├── BaseBuilder #1989
+│ │ ├── BaseConnection #2042
+│ │ ├── BaseResult #2002
+│ │ ├── Forge #2042
+│ │ ├── MigrateRollback #2035
+│ │ └── MigrationRunner #2019
+│ ├── Debug/
+│ │ └── Toolbar/Collectors/Routes #2030
+│ ├── Exceptions.
+│ │ └── ModelException #2054
+│ ├── Files/
+│ │ └── File #2104
+│ ├── Filters/
+│ │ └── Filters #2039
+│ ├── helpers/
+│ │ └── date_helper #2091
+│ ├── HTTP/
+│ │ ├── CLIRequest #2024
+│ │ ├── CURLRequest #1996, #2050
+│ │ ├── IncomingRequest #2063
+│ │ └── Request #2024
+│ ├── Language/en/
+│ │ └── Database #2054
+│ ├── Pager/
+│ │ └── Pager #2026
+│ ├── Router/
+│ │ ├── RouteCollection #1959, #2012, #2024
+│ │ ├── Router #2024, #2031, #2043
+│ │ └── RouterInterface #2024
+│ ├── Session/
+│ │ └── Handlers/ArrayHandler #2014
+│ ├── Test/
+│ │ ├── CIUnitTestCase #2002
+│ │ └── FeatureTestCase #2043
+│ ├── Throttle/
+│ │ └── Throttler #2074
+│ ├── CodeIgniter #2012, #2024
+│ ├── Common #2036
+│ ├── Entity #2002, #2004, #2011, #2081
+│ └── Model #2050, #2051, #2053, #2054
+├── tests/system/
+│ ├── CLI/
+│ │ └── ConsoleTest #2024
+│ ├── Database/
+│ │ ├── Live/
+│ │ │ ├── DbUtilsTest #2051, #2053
+│ │ │ ├── ForgeTest #2019, #2042
+│ │ │ ├── ModelTest #2002, #2051, #2053, #2054
+│ │ │ ├── SQLite/AlterTablesTest #2042
+│ │ │ └── WhereTest #2052
+│ │ └── Migrations/MigrationRunnerTest #2019
+│ ├── HTTP/
+│ │ ├── CLIRequest #2024
+│ │ └── CURLRequestTest #1996
+│ ├── Router/
+│ │ ├── RouteCollectionTest #1959, #2012, #2024
+│ │ └── RouterTest #2024, #2043
+│ ├── Test/
+│ │ └── FeatureTestCaseTest #2043
+│ ├── Throttle/
+│ │ └── ThrottleTest #2074
+│ ├── View/
+│ │ └── ParserTest #2005
+│ ├── CodeIgniterTest #2024
+│ └── EntityTest #2002, #2004
+└── user_guide_src/
+ ├── concepts/
+ │ └── autoloader #2035, #2071
+ ├── database/
+ │ └── query_builder #2035
+ ├── dbmgmt/
+ │ ├── forge #2042
+ │ └── migration #2042
+ ├── helpers/
+ │ └── date_helper #2091
+ ├── incoming/
+ │ └── routing #2035
+ ├── installation/
+ │ └── installing_composer #2015, #2035
+ ├── libraries/
+ │ ├── pagination #2026
+ │ ├── sessions #2014, #2035
+ │ ├── validation #2069
+ │ └── uploaded_files #2104
+ ├── models/
+ │ ├── entities #2002, #2004, #2035
+ │ └── model #2051, #2053, #2054
+ ├── outgoing/
+ │ └── view_parser #e21823, 32005
+ └── testing/
+ └── database #2051, #2053
+```
+
+## PRs merged
+
+- \#2104 File & UploadFile Fixes
+- \#2091 Timezone select
+- \#2081 JSON format checking improved
+- \#2079 Update config() to check all namespaces
+- \#2074 Throttler can access bucket for bucket life time
+- \#2071 Fix autoloader.rst formatting
+- \#2069 validation rule: then -> than (spelling)
+- \#2064 Bugfix file locator slash error
+- \#2063 Ensure query vars are part of request->uri. Fixes #2062
+- \#2060 Cache Drive Backups
+- \#2059 Add multi-path support to `locateFile()`
+- \#2054 Add model exceptions for missing/invalid dateFormat
+- \#2053 Change Model's deleted flag to a deleted_at datetime/timestamp. Fixes #2041
+- \#2052 Add various tests for (not) null
+- \#2051 Soft deletes use deleted_at
+- \#2050 Stash insert ID before event trigger
+- \#2043 Zero params should be passed through when routing. Fixes #2032
+- \#2042 SQLite3 now supports dropping foreign keys. Fixes #1982
+- \#2040 Update CURLRequest.php
+- \#2039 Restrict filter matching of uris so they require an exact match. Fixes #2038
+- \#2036 Make `force_https()` send headers before exit
+- \#2035 Various typos and Guide corrections
+- \#2031 Fallback to server request for default method
+- \#2030 Support the new `router` service in Debug Toolbar
+- \#2026 Extension Pager::makeLinks (optional group name)
+- \#2024 Refactor the way the router and route collection determine the current HTTP verb
+- \#2019 SQLite and Mysql driver additional tests and migration runner test fixes
+- \#2015 Direct user to follow the upgrade steps after installation
+- \#2014 Added a new Session/ArrayHandler that can be used during testing
+- \#2012 Use request->method for HTTP verb
+- \#2011 Set the raw data array without any mutations for the Entity
+- \#2008 Add `patch` method to command "routes"
+- \#2005 Plugin closures docs update and test
+- \#2004 Allow hasChanged() without parameter
+- \#2002 Entity Refactor
+- \#1999 use CodeIgniterController; not needed since Home Controller extends ...
+- \#1996 Attempting to fix CURLRequest debug issue. #1994
+- \#e21823 Corrected docs for parser plugins. Closes #1995
+- \#1989 argument set() must be of type string - cannot agree
+- \#1959 Prevent reverseRoute from searching closures
diff --git a/docs/changelogs/v4.0.0-rc.1.md b/docs/changelogs/v4.0.0-rc.1.md
new file mode 100644
index 000000000000..823dc0e159b7
--- /dev/null
+++ b/docs/changelogs/v4.0.0-rc.1.md
@@ -0,0 +1,229 @@
+# Version 4.0.0-rc.1
+
+Release Date: Sep 3, 2019
+
+**RC.1 release of CodeIgniter4**
+
+## Enhancements
+
+- CI3 Email ported to CI4
+- Encryption (basic) added
+- Migrations refactored and streamlined for more holistic functionality (BC)
+- Added `convert()` to `ImageHandlerInterface`
+- Disabled debug toolbar for downloads
+- CLI commands return an error code now ("spark" changed)
+- RESTful controllers added to shorten dev time for RESTful APIs
+- Added `RouteCollection::presenter()` as part of the RESTful support
+
+## App Changes
+
+- Added **app/Common.php** to make it easier to override common functions
+- `Config/Email` and `Config/Encryption` added
+- `Config/Migration` modified, and has different settings
+- **app/Controllers/Home.php** fixed, removing unnecessary model reference
+
+## Message Changes
+
+- Migration has new & modified messages
+- Messages now have a RESTful set
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── admin/
+│ ├── release-appstarter #2155
+│ └── release-framework #2155
+├── app/
+│ ├── Config/
+│ │ ├── Email #2092
+│ │ ├── Encryption #2135
+│ │ └── Migrations #2065
+│ ├── Controllers/
+│ │ ├── BaseController #2046
+│ │ └── Home #2145
+│ └── Common #2110
+├── public/
+├── system/
+│ ├── API/
+│ │ └── ResponseTrait #2131
+│ ├── Autoloader/
+│ │ ├── Autoloader #2149
+│ │ └── FileLocator #2149
+│ ├── Cache/Handlers/
+│ │ └── RedisHandler #2144
+│ ├── CLI/
+│ │ └── CommandRunner #2164
+│ ├── Commands/Database/
+│ │ ├── CreateMigration #2065
+│ │ ├── Migrate #2065, 2137
+│ │ ├── MigrateRefresh #2065, 2137
+│ │ ├── MigrateRollback #2065, 2137
+│ │ ├── MigrateStatus #2137
+│ │ └── MigrateVersion #2137
+│ ├── Config/
+│ │ ├── BaseConfig #2082
+│ │ └── Services #2135, 2092
+│ ├── Database/
+│ │ ├── BaseBuilder #2127, 2090, 2142, 2153, 2160, 2023, 2001
+│ │ └── MigrationRunner #2065, 2137
+│ ├── Debug/
+│ │ └── Toolbar #2118
+│ ├── Email/
+│ │ └── Email #2092
+│ ├── Encryption/
+│ │ ├── EncrypterInterface #2135
+│ │ ├── Encryption #2135
+│ │ ├── Exceptions/EncryptionException #2135
+│ │ └── Handlers/
+│ │ ├── BaseHandler #2135
+│ │ └── OpenSSLHandler #2135
+│ ├── Exceptions/
+│ │ └── ConfigException #2065
+│ ├── Files/
+│ │ └── File #2178
+│ ├── Filters/
+│ │ └── DebugToolbar #2118
+│ ├── Helpers/
+│ │ └── inflector_helper #2065
+│ ├── Honeypot/
+│ │ └── Honeypot #2177
+│ ├── HTTP/
+│ │ ├── DownloadResponse #2129
+│ │ ├── Files/UploadedFile #2128
+│ │ ├── Message @2171
+│ │ └── Response #2166
+│ ├── Images/
+│ │ ├── Handlers/
+│ │ │ ├── BaseHandler #2113, 2150
+│ │ │ └── ImageMagickHandler #2151
+│ │ └── ImageHandlerInterface #2113
+│ ├── Language/en/
+│ │ ├── Email #2092
+│ │ ├── Encryption #2135
+│ │ ├── Migrations #2065, 2137
+│ │ └── RESTful #2165
+│ ├── RESTful/
+│ │ ├── ResourceController #2165
+│ │ └── ResourcePresenter #2165
+│ ├── Router/
+│ │ └── RouteCollection #2165
+│ ├── Security/
+│ │ └── Security #2027
+│ ├── Session/Handlers/
+│ │ └── RedisHandler #2125
+│ ├── Test/
+│ │ └── CIDatabaseTestCase #2137
+│ ├── bootstrap #2110
+│ ├── CodeIgniter #2126, 2164
+│ ├── Common #2109
+│ ├── Entity #2112
+│ └── Model #2090
+├── tests/_support/
+│ └── RESTful/... #2165
+├── tests/system/
+│ ├── API/
+│ │ └── ResponseTraitTest #2131
+│ ├── Database/
+│ │ ├── Builder/
+│ │ │ ├── GetTest #2142
+│ │ │ ├── SelectTest #2160
+│ │ │ └── WhereTest #2001
+│ │ ├── Live/
+│ │ │ ├── GroupTest #2160
+│ │ │ ├── ModelTest #2090
+│ │ │ └── SelectTest #2160
+│ │ └── Migrations/MigrationRunnerTest #2065, 2137
+│ ├── Encryption/
+│ │ ├── EncryptionTest #2135
+│ │ └── OpenSSLHandlerTest #2135
+│ ├── Helpers/
+│ │ └── InflectorHelperTest #2065
+│ ├── HTTP/
+│ │ ├── DownloadResponseTest #2129
+│ │ └── MessageTest #2171
+│ ├── Images/
+│ │ └── GDHandlerTest #2113
+│ ├── RESTful/
+│ │ ├── ResourceControllerTest #2165
+│ │ └── ResourcePresenterTest #2165
+│ ├── Router/
+│ │ └── RouteCollectionTest #2165
+│ ├── ControllerTest #2165
+│ └── EntityTest #2112
+├── user_guide_src/
+│ ├── changelogs/
+│ │ └── next #2154
+│ ├── database/
+│ │ └── query_builder #2160, 2001
+│ ├── dbmgmt/
+│ │ └── migrations #2065, 2132, 2136, 2154, 2137
+│ ├── extending/
+│ │ └── common #2162
+│ ├── helpers/
+│ │ └── inflector_helper #2065
+│ ├── incoming/
+│ │ ├── restful #2165
+│ │ └── routing #2165
+│ ├── libraries/
+│ │ ├── email #2092, 2154
+│ │ ├── encryption #2135
+│ │ └── images #2113, 2169
+│ ├── outgoing/
+│ │ ├── api_responses #2131
+│ │ ├── localization #2134
+│ │ └── response #2129
+│ └── testing/
+│ └── database #2137
+├── CONTRIBUTING.md #2010
+├── README.md #2010
+└── spark
+```
+
+## PRs merged
+
+- \#2178 Add fallback for missing finfo_open
+- \#2177 Fix missing form close tag
+- \#2171 Setheader dupes
+- \#2169 Add $quality usage for Image Library
+- \#2166 Cookie error
+- \#2165 RESTful help
+- \#2164 Exit error code on CLI Command failure
+- \#2162 User Guide updates for Common.php
+- \#2160 Add BaseBuilder SelectCount
+- \#2155 Include .gitignore in starters
+- \#2153 Bug fix countAllResults with LIMIT
+- \#2154 Fix email & migrations docs; update changelog
+- \#2151 ImageMagick->save() return value
+- \#2150 New logic for Image->fit()
+- \#2149 listNamespaceFiles: Ensure trailing slash
+- \#2145 Remove UserModel reference from Home controller
+- \#2144 Update Redis legacy function
+- \#2142 Fixing BuilderBase resetting when getting the SQL
+- \#2137 New Migration Logic
+- \#2136 Migrations user guide fixes
+- \#2135 Encryption
+- \#2134 Fix localization writeup
+- \#2132 Update migration User Guide
+- \#2131 Added No Content response to APIResponseTrait
+- \#2129 Add setFileName() to DownloadResponse
+- \#2128 guessExtension fallback to clientExtension
+- \#2127 Update limit function since $offset is nullable
+- \#2126 Limit storePreviousURL to certain requests
+- \#2125 Updated redis session handler to support redis 5.0.x
+- \#2118 Disabled Toolbar on downloads
+- \#2113 Add Image->convert()
+- \#2112 Update `Entity.php` `__isset` method
+- \#2110 Added app/Common.php
+- \#2109 Fix typo in checking if exists db_connect()
+- \#2092 Original email port
+- \#2090 Fix prevent soft delete all without conditions set
+- \#2082 Update BaseConfig.php
+- \#2065 Migration updates for more holistic functionality
+- \#2046 clean base controller code
+- \#2027 Fix CSRF hash regeneration
+- \#2023 whereIn $value does not have to be an array
+- \#2010 WIP Verbiage revisions
+- \#2001 Subqueries in BaseBuilder
diff --git a/docs/changelogs/v4.0.0-rc.2.md b/docs/changelogs/v4.0.0-rc.2.md
new file mode 100644
index 000000000000..a94bb4e0f423
--- /dev/null
+++ b/docs/changelogs/v4.0.0-rc.2.md
@@ -0,0 +1,201 @@
+# Version 4.0.0-rc.2
+
+Release Date: Sept 27, 2019
+
+**RC.2 release of CodeIgniter4**
+
+## Enhancements
+
+- Query builder testability simplified with new property, but dropped method parameters (breaking change)
+- Database, migrations and sessions beefed up
+- Numerous smaller bugs corrected
+
+## App Changes
+
+- **app/Config/Constants.php**, **app/Config/Paths.php** & some config settings have had changes
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── admin/
+├── app/
+│ └── Config/
+│ ├── Boot/* #2241
+│ ├── Constants #2183
+│ └── Paths #2181
+├── public/
+├── system/
+│ ├── CLI/
+│ │ └── BaseCommand #2231
+│ ├── Database/
+│ │ ├── MySQLi/Connection #2201, 2229
+│ │ ├── Postgre/
+│ │ │ ├── BaseBuilder #2269
+│ │ │ └── Connection #2201
+│ │ ├── SQLite3/Connection #2201, 2228, 2230
+│ │ ├── BaseBuilder #2257, 2232, 2269, 2270
+│ │ ├── BaseConnection #2208, 2213, 2231
+│ │ ├── Config #2224
+│ │ ├── Forge #2205
+│ │ └── MigrationRunner #2191
+│ ├── Debug/
+│ │ └── Exceptions #2262
+│ ├── Encryption/
+│ │ ├── Encryption #2231
+│ │ └── Handlers/BaseHandler #2231
+│ ├── Files/
+│ │ └── FileCollection #2265
+│ ├── HTTP/
+│ │ ├── CURLRequest #2168
+│ │ ├── IncomingRequest #2265
+│ │ ├── Request #2253
+│ │ └── Response #2253
+│ ├── I18n/
+│ │ ├── Time #2231
+│ │ └── TimeDifference #2231
+│ ├── Images/
+│ │ └── Handlers/BaseHandler #2246
+│ ├── RESTful/
+│ │ └── ResourcePresenter #2271
+│ ├── Security/
+│ │ └── Security #2240
+│ ├── Session/
+│ │ └── Session #2197, 2231
+│ ├── Test/
+│ │ ├── CIDatabaseTestCase #2205
+│ │ └── CIDatabaseUnitTestCase #2184
+│ ├── Validation/
+│ │ ├── FileRules #2265
+│ │ └── Validation #2268
+│ ├── View/
+│ │ └── Parser #2264
+│ ├── Common #2200, 2209, 2261
+│ └── Model #2231
+├── tests/_support/
+├── tests/system/
+│ ├── Commands/
+│ │ └── CommandClassTest #2231
+│ ├── Database/
+│ │ ├── Builder/
+│ │ │ ├── GetTest #2232
+│ │ │ ├── CountTest #2269
+│ │ │ ├── DeleteTest #2269
+│ │ │ ├── EmptyTest #2269
+│ │ │ ├── GetTest #2269
+│ │ │ ├── GroupTest #2257
+│ │ │ ├── InsertTest #2269
+│ │ │ ├── ReplaceTest #2269
+│ │ │ ├── TruncateTest #2269
+│ │ │ └── UpdateTest #2269
+│ │ ├── Live/
+│ │ │ ├── EscapeTest #2229
+│ │ │ ├── ForgeTest #2201, 2211
+│ │ │ ├── GroupTest #2257
+│ │ │ ├── MetadataTest #2211
+│ │ │ └── ModelTest #2231
+│ │ └── BaseConnectionTest #2229, 2231
+│ ├── Encryption/
+│ │ └── EncryptionTest #2231
+│ ├── Helpers/
+│ │ └── URLHelperTest #2259
+│ ├── HTTP/
+│ │ ├── CURLRequestTest #2168
+│ │ ├── FileCollectionTest #2265
+│ │ └── URITest #2259
+│ ├── I18n/
+│ │ ├── TimeDifferenceTest #2231
+│ │ └── TimeTest #2231
+│ ├── Pager/
+│ │ └── pagerTest #2259
+│ ├── RESTful/
+│ │ └── ResourcePresenterTest #2271
+│ ├── Session/
+│ │ └── SessionTest #2231
+│ └── View/
+│ └── ParserTest #2264
+└── user_guide_src/
+ ├── concepts/
+ │ └── structure #2221
+ ├── database/
+ │ ├── metadata #2199, 2201, 2208
+ │ ├── queries #2208
+ │ └── query_builder #2257, 2232, 2269
+ ├── dbmgmt/
+ │ └── migration #2190, 2191
+ ├── extending/
+ │ └── contributing #2221
+ ├── general/
+ │ └── errors #2221
+ ├── helpera/
+ │ └── url_helper #2259
+ ├── incoming/
+ │ ├── restful #2189
+ │ └── routing #2221
+ ├── installation/
+ │ └── troubleshooting #2260
+ ├── libraries/
+ │ ├── encryption #2221
+ │ ├── pagination #2216
+ │ ├── time #2221
+ │ └── uti #2216
+ ├── outgoing/
+ │ ├── api_responses #2245
+ │ ├── view_layouts #2218
+ │ └── view_parser #2218, 2264
+ ├── testing/
+ │ ├── controllers #2221
+ │ ├── debugging #2221, 2209
+ │ ├── feature #2218, 2221
+ │ └── overview #2221
+ └── tutorial/
+ ├── news_section #2221
+ └── static_pages #2221
+```
+
+## PRs merged
+
+- \#2271 fix ResourcePresenter::setModel()
+- \#2270 groupStart() refactorization
+- \#2269 testMode() method for BaseBuilder
+- \#2268 Validation session use only if exists
+- \#2267 Tests setUp and tearDown: void
+- \#2265 Fix a validation issue on multiple file upload
+- \#2264 fix. Parser allow other extension
+- \#2262 Fix parameter type in Debug/Exceptions
+- \#2261 Fix lang() signature
+- \#2260 Explain the whoops page
+- \#2259 Add URI & url_helper tests
+- \#2257 Several updates to the HAVING clauses
+- \#2253 Fix invalid parameters
+- \#2246 EXIF not supported for GIF
+- \#2245 Fix class ref parameter types
+- \#2241 Fix ini_set parameter type
+- \#2240 Handle JSON POSTs in CSRF
+- \#2232 Fixes BaseBuilder getWhere() bug
+- \#2231 Add magic \__isset to classes with \__get
+- \#2230 Add escape to SQLite \_listTables()
+- \#2229 MySQLi escapeLikeStringDirect()
+- \#2228 Exclude `sqlite_%` from listTables()
+- \#2224 change new ConfigDatabase() to config('Database')
+- \#2221 Documentation fixes
+- \#2218 Typo corrected
+- \#2216 Update uri.rst
+- \#2213 Filter listTables cache response on constrainPrefix
+- \#2211 Add listTable() tests
+- \#2209 Add trace()
+- \#2208 Add $db->getPrefix()
+- \#2205 Fix empty() bug on DBPrefix
+- \#2201 Foreign key columns
+- \#2200 Notify Kint of dd alias
+- \#2199 Add getForeignKeyData to User Guide
+- \#2187 Update Session.php
+- \#2191 Migration rollback reverse
+- \#2190 Fix name of ForeignKeyChecks
+- \#2189 missing return
+- \#2184 Fix case on "Seeds/" directory
+- \#2183 Check `defined` for constants
+- \#2181 Remove copy-paste extraneous text
+- \#2168 Fix for CURL for 'debug' option
diff --git a/docs/changelogs/v4.0.0-rc.3.md b/docs/changelogs/v4.0.0-rc.3.md
new file mode 100644
index 000000000000..993e0ca20a49
--- /dev/null
+++ b/docs/changelogs/v4.0.0-rc.3.md
@@ -0,0 +1,161 @@
+# Version 4.0.0-rc.3
+
+Release Date: Oct 19, 2019
+
+**RC.3 release of CodeIgniter4**
+
+## Enhancements
+
+- Beefed up database, session & routing handling.
+- Fixed numerous bugs & user guide errata.
+
+## App Changes
+
+- New `$CSRFHeaderName` property in **app/Config/App.php**
+
+## Changed Files
+
+The list of changed files follows, with PR numbers shown.
+
+```text
+.
+├── admin/
+├── app/
+│ └── Config/
+│ └── App #2272
+├── public/
+├── system/
+│ ├── Autoloader/
+│ │ └── FileLocator #2336
+│ ├── Database/
+│ │ ├── MySQLi/Forge #2100
+│ │ ├── Postgre/Forge #2100
+│ │ ├── SQLite3/Forge #2100
+│ │ ├── BaseBuilder #2252, 2312
+│ │ ├── Forge $2100
+│ │ ├── Migration #2303
+│ │ └── MigrationRunner #2303
+│ ├── Debug/
+│ │ ├── Exceptions #2288
+│ │ ├── Toolbar/Collectors/
+│ │ │ └── Route #2300
+│ │ ├── Toolbar #2315
+│ │ └── Views/ #2283
+│ ├── Helpers/
+│ │ ├── inflector_helper #2296
+│ │ └── url_helper #2325
+│ ├── HTTP/
+│ │ ├── CURLRequest #2285, 2305
+│ │ └── Files/UploadedFile #2123
+│ ├── Language/en/
+│ │ ├── Encryption #2311
+│ │ ├── RESTful #2311
+│ │ └── Session #2311
+│ ├── Router/
+│ │ ├── Exceptions/RedirectException #2338
+│ │ └── Router #2308, 2338
+│ ├── Security/
+│ │ └── Security #2272, 2279
+│ ├── Session/
+│ │ ├── Handlers/
+│ │ │ ├── DatabaseHandler #2298
+│ │ │ ├── FileHandler #2298, 2307
+│ │ │ ├── MemcachedHandler #2298
+│ │ │ └── RedisHandler #2298
+│ │ └── Session #2339
+│ ├── Validation/
+│ │ └── Validation #2284, 2341
+│ ├── View/
+│ │ └── View #2324
+│ ├── CodeIgniter #2338
+│ ├── Common #2279
+│ └── Model #2289, 2332
+├── tests/README.md #2345
+├── tests/_support/
+│ └── Config/
+│ └── MockAppConfig #2272
+├── tests/system/
+│ ├── Database/
+│ │ ├── Builder/
+│ │ │ └── UpdateTest #2295
+│ │ └── Live/
+│ │ └── ForgeTest #2100
+│ ├── Helpers/
+│ │ ├── InflectorHelperTest #2296
+│ │ └── URLHelperTest #2325
+│ ├── HTTP/
+│ │ └── CURLRequestTest #2285
+│ ├── Log/
+│ │ └── FileHandlerTest #2346
+│ ├── Security/
+│ │ └── SecurityTest #2279
+│ ├── Session/
+│ │ └── SessionTest #2339
+│ └── CommonFunctionsTest #2279
+└── user_guide_src/
+ ├── dbmgmt/
+ │ ├── forge #2100
+ │ └── migration #2337
+ ├── general/
+ │ ├── common_functions #2279
+ │ ├── errors #2338
+ │ └── modules #2290
+ ├── helpers/
+ │ └── inflector_helper #2296
+ ├── incoming/
+ │ ├── message #2282
+ │ ├── restful #2313, 2321, 2333
+ │ └── routing #2327
+ ├── libraries/
+ │ ├── curlrequest #2305
+ │ └── security #2279
+ ├── models/
+ │ └── model #2316, 2332
+ └── outgoing/
+ └── table #2337
+```
+
+## PRs merged
+
+- \#2348 CodeIgniter Foundation gets copyright
+- \#2346 Fix FilerHandlerTest.php weirdness
+- \#2345 Tests readme polish
+- \#2344 Setup vs Set Up
+- \#2343 User guide minor fixes. Fix class names and code area
+- \#2341 Simplify Validation::getErrors()
+- \#2339 Fix Session::get('key') returns null when value is (int) 0
+- \#2338 Revert RedirectException change
+- \#2337 Guide: Minor grammar corrections
+- \#2336 Correct cleaning of namespaces in Windows
+- \#2333 Guide: RESTful table formatting
+- \#2332 Change after methods to use actual data
+- \#2328 Update Application structure
+- \#2327 Correct the routing UG page
+- \#2325 Fix bug in url_title() function with diacritics
+- \#2324 Renderer Toolbar Debug Toggle
+- \#2321 Update RESTful User Guide
+- \#2316 Add getValidationRules() to model UG page
+- \#2315 Enhance Toolbar::renderTimeline
+- \#2313 RESTful User Guide cleanup
+- \#2312 BaseBuilder variable type fix
+- \#2311 Convert all language returns to single quote
+- \#2308 Bugfix extra autoroute slashes
+- \#2307 Resolve session save handler issue
+- \#2305 Fix curl debug bug
+- \#2303 Use DBGroup variable from migration class if defined
+- \#2300 Routes collector for toolbar should not die when a method name is calculated through \_remap
+- \#2298 fix issue on session_regenerate
+- \#2296 Add counted() to Inflector Helper
+- \#2295 Test set() method in Builder class more
+- \#2290 Fix Code Modules documentation for psr4 namespace configuration
+- \#2289 Don't restrict model's access to properties in a read-only manner
+- \#2288 Fix line numbering in Debug/Exceptions class
+- \#2285 Fix error with Host header for CURLRequest class
+- \#2284 Fix getErrors() for validation with redirect
+- \#2283 Hotfix: Rename collectors \_\*.tpl.php to \_\*.tpl
+- \#2282 Fix user guide for Message class
+- \#2279 Bug in CSRF parameter cleanup
+- \#2272 Handle X-CSRF-TOKEN - CSRF
+- \#2252 Batch Update Where Reset
+- \#2123 WIP fix store() default value bug
+- \#2100 Added validation to check whether a database exists before creating it for MySQLi
diff --git a/docs/changelogs/v4.0.0-rc.4.md b/docs/changelogs/v4.0.0-rc.4.md
new file mode 100644
index 000000000000..40f2918aff36
--- /dev/null
+++ b/docs/changelogs/v4.0.0-rc.4.md
@@ -0,0 +1,91 @@
+# Version 4.0.0-rc.4
+
+Release Date: February 6, 2020
+
+**RC.4 release of CodeIgniter4**
+
+## Enhancements
+
+- Fixed URL systems so that they would work when the system is served out of subfolders.
+- Added required insert ignore support for SQLite3 and MySQL.
+- Add validation function `is_not_unique`
+- Various improvements and cleanup to the Email class
+
+## PRs merged
+
+- \#2527 Update manual.rst
+- \#2454 Page in the official documentation on ajax requests with iSAJAX() fixes
+- \#2525 Remove incorrect inline doc type
+- \#2524 Restore namespace after regress.
+- \#2523 Replace legacy CI3 constant.
+- \#2522 Adding Events information in the 'Upgrading from 3.x to 4.x' section
+- \#2518 Fix pager URI to work in subfolders.
+- \#2516 HTML Helper - Fix attribute type for lists
+- \#2515 Layout Renderer Fix
+- \#2513 Typo in userguide "Entity Classes - Business Logic"
+- \#2511 Database add highlight
+- \#2509 Revert Renderer section reset
+- \#2507 Update ordering of search locations for better prioritization.
+- \#2506 HTTP Response - Fix crash on CSP methods when CSP is disabled
+- \#2504 BaseConnection - Nullable return type in getConnectStart()
+- \#2502 View Renderer - Reset sections after generating the output
+- \#2501 view_cell call controller on initController method.
+- \#2499 View Parser - Fix ParsePair() with filter
+- \#2497 Fix splitQueryPart()
+- \#2496 Use site_url for RedirectResponse.
+- \#2495 update toolbar userguide
+- \#2494 Debug Toolbar - Fix Debugbar-Time header, Render in <head>
+- \#2493 fix sphinx version.
+- \#2490 fix. Toolbar init view Error
+- \#2489 Fix pager
+- \#2486 Update current_url and previous_url in the docs for View Parser.
+- \#2485 Typo in user guide "Running via the Command Line"
+- \#2482 Services request add URI Core System extend support
+- \#2481 Priority Redirection.
+- \#2472 ControllerTest should work without URI specified. Fixes #2470
+- \#2471 Transition from Zend Escaper to Laminas Escaper
+- \#2462 Fix impossible length for migration table id.
+- \#2458 Replace `composer install` by `composer require`
+- \#2450 CRITICAL when $_SESSION is null / Argument 2 passed to dot_array_search() must be []
+- \#2449 User Guide: Query Builder selectCount - error correction in example
+- \#2447 Existing File checks (Nowackipawel/patch-69)
+- \#2446 DB Insert Ignore (Tada5hi/database-feature)
+- \#2438 Nice array view in debug toolbar
+- \#2436 Fix Message method reference
+- \#2433 Inserting through a model should respect all validation rules. Fixes #2384
+- \#2432 Fix curly brace deprecation in php 7.4
+- \#2429 fix. safe_mailto multi-byte safe
+- \#2427 Add $recipients property to ConfigEmail
+- \#2426 Add hex validation rule, test, Guide
+- \#2425 fix: Router setDefaultNameSpace doesn't work
+- \#2422 Don't show duplicate Date headers when running under PHP's server.
+- \#2420 Change current_url() to use cloned URI
+- \#2417 Revise Encryption Service Documentation
+- \#2416 Add missing closing braces of condition 'hasError()'
+- \#2415 Add 'nullable' to MySQL field data
+- \#2413 fix. toolbar file 301
+- \#2411 fix parse params of plugin
+- \#2408 Ensure previous_url() gets accurate URI.
+- \#2407 Fix url helper functions to work when the site is hosted in subfolders.
+- \#2406 Fix issue #2391 CodeIgniter::display404errors()
+- \#2402 Removed pointless isset() check
+- \#2401 Remove pointless check from conditional
+- \#2400 Remove redundant check in conditionals
+- \#2399 Revise Controllers Documentation
+- \#2398 Edit .htaccess
+- \#2392 Add validation function `is_not_unique`
+- \#2389 Confer silent status to nested seeders
+- \#2388 Fix copypaste command comment
+- \#2387 Use only digits for migrations order
+- \#2382 quick fix postgresql insert id
+- \#2381 Fix: Use of CodeIgniterConfigServices prevents Service overriding
+- \#2379 Replace null log file extension check
+- \#2377 Docs Rev: Replacing Core Classes
+- \#2369 Remove LoggerAwareTrait from Email class
+- \#2368 Remove log_message from Email::\__construct
+- \#2364 Email config doesn't incorporate .env items
+- \#2362 Fix SMTP protocol problem
+- \#2359 Bugfix Model after event data
+- \#2358 Fix Logger config
+- \#2356 Fix typo in comments of Services.php
+- \#2352 Fix method name to 'toDateString()' in Date and Times user guide
diff --git a/docs/changelogs/v4.0.0.md b/docs/changelogs/v4.0.0.md
new file mode 100644
index 000000000000..c75a3abdcd6b
--- /dev/null
+++ b/docs/changelogs/v4.0.0.md
@@ -0,0 +1,23 @@
+# Version 4.0.0
+
+Release Date: February 24, 2020
+
+**4.0 release of CodeIgniter4**
+
+## Enhancements
+
+- Updated welcome view file
+- Debug Toolbar now supports dark mode
+- New `alpha_numeric_punct` validation rule
+- Kint was updated from the 2.x branch to the latest 3.x branch, with a new config file to specify options
+- Additional docs for getting started
+- CLI fixed to better handle complex arguments
+- Improvements to File class
+- New `model()` helper method for easy singleton `Models`.
+- Tests completely reorganized to make app-level tests simpler out of the box.
+
+## Repo Changes
+
+The `devstarter` repo has been deprecated.
+
+We now have an automated changelog generated in the [CHANGELOG.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md) file in the main repo. See it for more details.
diff --git a/docs/changelogs/v4.0.3.md b/docs/changelogs/v4.0.3.md
new file mode 100644
index 000000000000..2c18966b1c23
--- /dev/null
+++ b/docs/changelogs/v4.0.3.md
@@ -0,0 +1,26 @@
+# Version 4.0.3
+
+Release Date: May 1, 2020
+
+**4.0.3 release of CodeIgniter4**
+
+Thanks to the community, there have been 74 bugs closed, 21 issues closed, and 88 merged pull requests. These are all listed in the [CHANGELOG.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md). This is a small representation of some of the bigger changes you should know about.
+
+## Enhancements
+
+- The `API\ResponseTrait` now only determines the final formatting (json/xml) through content negotiation when the controller's `$format` variable is `null`. If it contains either `json` or `xml`, then that format will always be returned.
+- Pagination now can actually create next page and previous page links, instead of next/previous groups of links.
+- Windows users wanting to build their own copy of the user guide locally can now use the included `make.bat` file.
+- Locale matching in the IncomingRequest class now works to match broad groups, like `fr` even when the browser only supplies a more specific locale code, like `fr-FR`.
+- Added the ability to have nested language definitions.
+- Added the ability to replace {locale} with `request->getLocale()` in form_open('action').
+- Tables generated by CLI commands can now be colored.
+
+## Bugs Fixed
+
+- Deleting through the Model on a table with a string primary key now works.
+- Default pagination templates fixed to use the correct locale.
+- Lots of tweaks and corrections in the user guide.
+- Fixed locating files in custom namespaces that were occasionally not found. Primarily affected console commands.
+
+See the repo's [CHANGELOG_4.0.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.0.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.0.4.md b/docs/changelogs/v4.0.4.md
new file mode 100644
index 000000000000..6436ceb96e1b
--- /dev/null
+++ b/docs/changelogs/v4.0.4.md
@@ -0,0 +1,61 @@
+# Version 4.0.4
+
+Release Date: July 15, 2020
+
+**4.0.4 release of CodeIgniter4**
+
+## BREAKING
+
+- Added an `$arguments` parameter to `after()` and `before()` in `FilterInterface`. This is a breaking change, so all code implementing the `FilterInterface` must be updated.
+
+## Enhancements
+
+- The location for the SQLite3 database has changed and will now be located by default in a **writable/** folder instead of the **public/** folder.
+- New CLI command: `cache:clear` that will destroy all data within the current cache engine.
+- Thanks to samsonasik, we have several libraries brought up to 100% test coverage.
+- A few small performance increases. Every ounce matters!
+- `getFieldData()` in the database results class now returns column type and length.
+- Default .htaccess file updated to work better with extended characters.
+- New Testing Feature: [Fabricator](../testing/fabricator.md) makes creating mock classes simple and repeatable in your tests.
+- Model class can now have the callbacks overridden at runtime. Useful for testing.
+- A number of improvements to [Feature Tests](../testing/feature.md) in general.
+- New [command() helper function](../cli/spark_commands.md) to programmatically run your CLI commands. Useful for testing and cron jobs.
+- New command, `make:seeder` to generate a [Database Seed class](../dbmgmt/seeds.md) skeleton file.
+- Colors now available on the CLI within Windows, as well as other Windows-related CLI improvements.
+- New helper [mb_url_title()](../helpers/url_helper.md) that functions like `url_title()` but automatically escapes any extended URL characters.
+- [Image library](../libraries/images.md) now supports `webp` images.
+- Added Unicode support for regular expressions in the Router.
+- Added support for removing hidden folders in the [delete_files()](../helpers/filesystem_helper.md) helper.
+- `fetchGlobal()` in the Request class now supports applying filters to arrays of data, not just the first item.
+- `file` validation now works with arrays of files.
+- URI class now supports a `setSilent()` method that will disable the throwing of Exceptions.
+- New argument to `URI::getSegment()` that allows us to change the default value returned if nothing exists.
+- Implemented `withResource()` in Image so that you can use method chaining instead of using the provided `getResource()` (used in tests) to just compress your image.
+- Cookies and headers can be copied from the global Response object when doing a redirect using the new `withCookies()` and `withHeaders()` methods.
+- Support for the special prefix "hex2bin:" which can be used with the `$key` parameter in the [Encryption class](../libraries/encryption.md) or in the .env file.
+
+## Bugs Fixed
+
+- Fixed location for the SQLite3 database, which will now be located by default in a `writable` folder instead of the `public` folder.
+- Fixed bug where `force_https()` could add `https://` a second time.
+- Fixed a bug with CurlRequest that could result in incorrect "100 Continue" headers.
+- `Image::save()` bug fixed when `$target` parameter was `null`.
+- Fixes for `set_checkbox()` and `set_radio()` when the `$default` parameter is set to `true`.
+- Fix for result object handling in the Model class.
+- Fixed the escape character in the SQLite database.
+- Fix for inserts on Postgres and Entities when the primary key was null.
+- CLI scripts can now correctly recognize dashes within arguments.
+- CURLRequest now properly sets content length with multipart data.
+- Misc. stability improvements for the ImageMagick handler.
+- Setting validation errors within a config file should now work.
+- Unicode characters are not escaped when saving JSON from Entities.
+- Redirecting with a custom HTTP code should work correctly now.
+- `Time::setTimezone()` now works correctly.
+- Added full outer join support for Postgres.
+- Some cast items in the Entity (like array, json) were not being set correctly during a `fill()` process.
+- Fixed bug in Image GD handler that would try to compress images twice in certain cases.
+- Ensured that the translation output logic works on the selected locale, dashed locale, and fallback "en".
+- Fixed `is_unique`/`is_not_unique` validation called on POST/PUT via API in PostgreSQL.
+- Fixed a bug where filter arguments were not passed to `after()`.
+
+See the repo's [CHANGELOG_4.0.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.0.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.0.5.md b/docs/changelogs/v4.0.5.md
new file mode 100644
index 000000000000..f64407008d5e
--- /dev/null
+++ b/docs/changelogs/v4.0.5.md
@@ -0,0 +1,50 @@
+# Version 4.0.5
+
+Release Date: January 31, 2021
+
+**4.0.5 release of CodeIgniter4**
+
+## Enhancements
+
+- New URL helper function `url_to()` which creates absolute URLs based on controllers.
+- New Model option: `$useAutoIncrement` which when set to `false` allows you to provide your own primary key for each record in the table. Handy when we want to implement a 1:1 relation or use UUIDs for our model.
+- New URL helper function `url_is()` which allows you to check the current URL to see if it matches the given string.
+- Services now have their config parameters strictly typehinted. This will ensure no one will pass a different config instance. If you need to pass a new config with additional properties, you need to extend that particular config.
+- Support for setting the SameSite attribute on Session and CSRF cookies has been added. For security and compatibility with the latest browser versions, the default setting is `Lax`.
+- Guessing file extensions from mime type in `Config\Mimes::guessExtensionFromType()` now only reverse searches the `$mimes` array if no extension is proposed (i.e., usually not for uploaded files).
+- The getter functions for file extensions of uploaded files now have different fallback values (`UploadedFile::getClientExtension()` for `UploadedFile::getExtension()` and `''` for `UploadedFile::guessExtension()`). This is a security fix and makes the process less dependent on the client.
+- The Cache `FileHandler` now allows setting the file permissions mode via `Config\Cache`.
+
+## Changes
+
+- System messages defined in **system/Language/en/** are now strictly for internal framework use and are not covered by the backwards compatibility (BC) promise. Users may use these messages in their applications but at their own risk.
+
+## Deprecations
+
+- Deprecated `BaseCommand::getPad()` in favor of `BaseCommand::setPad()`.
+- Deprecated `CodeIgniter\Controller::loadHelpers()` in favor of the `helper()` function.
+- Deprecated `Config\Format::getFormatter()` in favor of `CodeIgniter\Format\Format::getFormatter()`
+- Deprecated `CodeIgniter\Security\Security::CSRFVerify()` in favor of `CodeIgniter\Security\Security::verify()`
+- Deprecated `CodeIgniter\Security\Security::getCSRFHash()` in favor of `CodeIgniter\Security\Security::getHash()`
+- Deprecated `CodeIgniter\Security\Security::getCSRTokenName()` in favor of `CodeIgniter\Security\Security::getTokenName()`
+- Deprecated `Config\App::$CSRFTokenName` in favor of `Config\Security::$tokenName`
+- Deprecated `Config\App::$CSRFHeaderName` in favor of `Config\Security::$headerName`
+- Deprecated `Config\App::$CSRFCookieName` in favor of `Config\Security::$cookieName`
+- Deprecated `Config\App::$CSRFExpire` in favor of `Config\Security::$expire`
+- Deprecated `Config\App::$CSRFRegenerate` in favor of `Config\Security::$regenerate`
+- Deprecated `Config\App::$CSRFRedirect` in favor of `Config\Security::$redirect`
+- Deprecated `Config\App::$CSRFSameSite` in favor of `Config\Security::$samesite`
+- Deprecated the `migrate:create` command in favor of the `make:migration` command.
+- Deprecated `CodeIgniter\Database\ModelFactory` in favor of `CodeIgniter\Config\Factories::models()`
+- Deprecated `CodeIgniter\Config\Config` in favor of `CodeIgniter\Config\Factories::config()`
+- Deprecated `CodeIgniter\HTTP\Message::getHeader()` in favor of `header()` to prepare for PSR-7
+- Deprecated `CodeIgniter\HTTP\Message::getHeaders()` in favor of `headers()` to prepare for PSR-7
+- Deprecated `Config\Cache::$storePath` in favor of `Config\Cache::$file['storePath']`
+
+## Bugs Fixed
+
+- Fixed a bug in the `Entity` class where declaring class parameters was preventing data propagation to the `attributes` array.
+- Handling for the environment variable `encryption.key` has changed. Previously, explicit function calls, like `getenv('encryption.key')` or `env('encryption.key')` where the value has the special prefix `hex2bin:`, returned an automatically converted binary string. These calls now return the character string with the prefix. This change was due to incompatibility with handling binary strings in environment variables on Windows platforms. However, accessing `$key` using the `Encryption` class config remains unchanged and still returns a binary string.
+- `Config\Services` (in **app/Config/Services.php**) now extends `CodeIgniter\Config\BaseService` to allow proper discovery of third-party services.
+
+See the repo's [CHANGELOG_4.0.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.0.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.1.0.md b/docs/changelogs/v4.1.0.md
new file mode 100644
index 000000000000..52d61ed624e3
--- /dev/null
+++ b/docs/changelogs/v4.1.0.md
@@ -0,0 +1,21 @@
+# Version 4.1.0
+
+Release Date: January 31, 2021
+
+**4.1.0 release of CodeIgniter4**
+
+## BREAKING
+
+- The `Autoloader::loadLegacy()` method was previously used for migration of non-namespaced classes in transition to CodeIgniter v4. Since v4.1.0, this support was removed.
+
+## Changes
+
+- Dropped support for PHP 7.2
+
+## Deprecations
+
+- Deprecated `Model::fillPlaceholders()` method. Use `Validation::fillPlaceholders()` instead.
+
+## Bugs Fixed
+
+See the repo's [CHANGELOG_4.1.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.1.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.1.1.md b/docs/changelogs/v4.1.1.md
new file mode 100644
index 000000000000..d0aaaf12ad4a
--- /dev/null
+++ b/docs/changelogs/v4.1.1.md
@@ -0,0 +1,13 @@
+# Version 4.1.1
+
+Release Date: February 1, 2021
+
+**4.1.1 release of CodeIgniter4**
+
+## Bugs Fixed
+
+- Fixed an issue where `.gitattributes` was preventing framework downloads.
+
+Note that this fix was also applied retroactively to 4.0.5 on the **framework** repo.
+
+See the repo's [CHANGELOG_4.1.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.1.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.1.2.md b/docs/changelogs/v4.1.2.md
new file mode 100644
index 000000000000..9f44ce12797b
--- /dev/null
+++ b/docs/changelogs/v4.1.2.md
@@ -0,0 +1,56 @@
+# Version 4.1.2
+
+Release Date: May 16, 2021
+
+**4.1.2 release of CodeIgniter4**
+
+## BREAKING
+
+Fixed [a bug](https://github.com/codeigniter4/CodeIgniter4/issues/4116) in `current_url()` that prevented configurations with an `indexPage` from including that value in the return value. Any installations using `App::$indexPage` should expect altered values from `current_url()` and all its dependencies (including Response Testing, Pager, Form Helper, and View Parser).
+
+## 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 lowers the priority of specific route processing.
+- The `Autoloader` class can now load files which do not contain PHP classes. The non-class files are listed in the `$files` property of the `Config\Autoload` class.
+
+## Changes
+
+- Layouts in views now support nested sections.
+- `Response::getCookie` now returns a `Cookie` instance instead of an array of cookie attributes.
+- `Response::getCookies` now returns an array of `Cookie` instances instead of an array of arrays of attributes.
+- To eliminate warnings from modern browsers' consoles, empty samesite values will be defaulted to `Lax` on cookie dispatch.
+- `Model::errors()` and `BaseModel::errors()` now always return `array`; there was no definition change but the docblock has been updated.
+- `Entity::castAs` has changed behavior. Added new parameter `$method`. Type casting has been moved to separate handlers.
+- Entity. Timestamp casting now throws an exception when an invalid value is passed
+- `Entity::castAsJson` uses external cast handler `JsonCast::get`.
+- `Entity::mutateDate` uses external cast handler `DatetimeCast::get`.
+- In order for `Config\**` classes to get their respective properties' values from the `.env`, it is now necessary to namespace the property with the name of the class. Previously, the property names were enough, but this is now disallowed because it can get system environment variables, like `PATH`.
+- The array helper `_array_search_dot` is now marked for `@internal` use. As this is used by `dot_array_search`, users should not use `_array_search_dot` directly in their code.
+- `CacheInterface::getMetaData()` returns `null` for misses, or an array with at least the "expires" key set to the absolute epoch expiration, or `null` for "never expires". The File, Memcached, and Wincache Handlers still return `false` which will become `null` in a future release.
+
+## Deprecations
+
+- Deprecated `CodeIgniter\View\View::$currentSection` property.
+- Language strings and exceptions for invalid cookie samesite values are deprecated in favor of `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()` in favor of `Cookie`'s internal expiration 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
+
+## Bugs Fixed
+
+- `BaseConnection::query()` now returns `false` for failed queries (unless `DBDebug==true`, in which case an exception will be thrown) and returns boolean values for write-type queries as specified in the docs.
+
+See the repo's [CHANGELOG_4.1.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.1.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.1.3.md b/docs/changelogs/v4.1.3.md
new file mode 100644
index 000000000000..c7576c6456e4
--- /dev/null
+++ b/docs/changelogs/v4.1.3.md
@@ -0,0 +1,26 @@
+# Version 4.1.3
+
+Release Date: June 6, 2021
+
+**4.1.3 release of CodeIgniter4**
+
+## Enhancements
+
+- New functions in the File Helper: `directory_mirror()` and `same_file()`
+- Implemented NexusPHP's `Tachycardia` for slow test identification
+- Added a new `$ttl` option to `Cache` config for future use
+
+## Changes
+
+- Added MySQL 8.0 to the test matrix
+- Improved environment detection from `$_SERVER`
+- Numerous sweeping code improvements via Rector and analysis
+
+## Bugs Fixed
+
+- Fixed a bug where `CURLRequest` would try to use a project URI instead of its base
+- Fixed a bug where CLI mode was not detected under `cgi-fcgi`
+- Fixed a logic bug in Cookie construction
+- Fixed numerous issues in SQLite3's `Forge` class related to an incorrect attribute name
+
+See the repo's [CHANGELOG_4.1.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.1.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.1.4.md b/docs/changelogs/v4.1.4.md
new file mode 100644
index 000000000000..00e62e3de8b7
--- /dev/null
+++ b/docs/changelogs/v4.1.4.md
@@ -0,0 +1,49 @@
+# Version 4.1.4
+
+Release Date: September 6, 2021
+
+**4.1.4 release of CodeIgniter4**
+
+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).
+
+## BREAKING
+
+- The following methods were changed from "public" to "protected" to match their parent class methods and better align with their uses:
+ - `CodeIgniter\Database\MySQLi\Connection::execute()`
+ - `CodeIgniter\Database\MySQLi\Connection::_fieldData()`
+ - `CodeIgniter\Database\MySQLi\Connection::_indexData()`
+ - `CodeIgniter\Database\MySQLi\Connection::_foreignKeyData()`
+ - `CodeIgniter\Database\Postgre\Builder::_like_statement()`
+ - `CodeIgniter\Database\Postgre\Connection::execute()`
+ - `CodeIgniter\Database\Postgre\Connection::_fieldData()`
+ - `CodeIgniter\Database\Postgre\Connection::_indexData()`
+ - `CodeIgniter\Database\Postgre\Connection::_foreignKeyData()`
+ - `CodeIgniter\Database\SQLSRV\Connection::execute()`
+ - `CodeIgniter\Database\SQLSRV\Connection::_fieldData()`
+ - `CodeIgniter\Database\SQLSRV\Connection::_indexData()`
+ - `CodeIgniter\Database\SQLSRV\Connection::_foreignKeyData()`
+ - `CodeIgniter\Database\SQLite3\Connection::execute()`
+ - `CodeIgniter\Database\SQLite3\Connection::_fieldData()`
+ - `CodeIgniter\Database\SQLite3\Connection::_indexData()`
+ - `CodeIgniter\Database\SQLite3\Connection::_foreignKeyData()`
+ - `CodeIgniter\Images\Handlers\GDHandler::_flatten()`
+ - `CodeIgniter\Images\Handlers\GDHandler::_flip()`
+ - `CodeIgniter\Images\Handlers\ImageMagickHandler::_flatten()`
+ - `CodeIgniter\Images\Handlers\ImageMagickHandler::_flip()`
+ - `CodeIgniter\Test\Mock\MockIncomingRequest::detectURI()`
+ - `CodeIgniter\Test\Mock\MockSecurity::sendCookie()`
+- To be compatible with the strict inheritance checks of PHP 8.1, return types were added to the following method signatures to match their parents' signatures whenever possible:
+ - `CodeIgniter\Cookie\Cookie::offsetExists()`
+ - `CodeIgniter\Cookie\Cookie::offsetSet()`
+ - `CodeIgniter\Cookie\Cookie::offsetUnset()`
+ - `CodeIgniter\Cookie\CookieStore::getIterator()`
+ - `CodeIgniter\I18n\Time::__wakeup()`
+ - `CodeIgniter\Test\Filters\CITestStreamFilter::filter()`
+- Related to the strict inheritance checks of PHP 8.1, the following session handlers implementing `SessionHandlerInterface` have their public methods modified to match the interface:
+ - `CodeIgniter\Session\Handlers\ArrayHandler`
+ - `CodeIgniter\Session\Handlers\DatabaseHandler`
+ - `CodeIgniter\Session\Handlers\FileHandler`
+ - `CodeIgniter\Session\Handlers\MemcachedHandler`
+ - `CodeIgniter\Session\Handlers\RedisHandler`
+
+See the repo's [CHANGELOG_4.1.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.1.md) for a complete list of changes.
diff --git a/docs/changelogs/v4.1.5.md b/docs/changelogs/v4.1.5.md
new file mode 100644
index 000000000000..c645044758c1
--- /dev/null
+++ b/docs/changelogs/v4.1.5.md
@@ -0,0 +1,38 @@
+# Version 4.1.5
+
+Release Date: November 8, 2021
+
+**4.1.5 release of CodeIgniter4**
+
+## BREAKING
+
+- Fixed [a bug](https://github.com/codeigniter4/CodeIgniter4/issues/2913) in CSRF protection. Now CSRF protection works on PUT/PATCH/DELETE requests when the CSRF filter is applied. If you use such requests, you need to send the CSRF token.
+- In the previous version, if you didn't provide your own headers, `CURLRequest` would send the request-headers from the browser, due to a bug. As of this version, it does not send them.
+- Fixed `BaseBuilder::insertBatch()` return value for `testMode`. Now it returns an SQL string array instead of the number of affected rows. This change was made to maintain compatibility between the return types of batch methods. Now the returned data type for `BaseBuilder::insertBatch()` is the same as for the `updateBatch()` method.
+- Major optimizations have been made to the way data is processed in `BaseBuilder::insertBatch()` and `BaseBuilder::updateBatch()` methods. This resulted in reduced memory usage and faster query processing. As a trade-off, the result generated by the `$query->getOriginalQuery()` method was changed. It no longer returns the query with the bound parameters, but the actual query that was run.
+
+## Enhancements
+
+- Added Cache config for reserved characters
+- The [addForeignKey()](../dbmgmt/forge.md#adding-foreign-keys) function of the `Forge` class can now define composite foreign keys in an array
+- The `dropKey` function of the `Forge` class can remove a key
+- Now `_` can be used as a separator in environment variables. See [Namespace Separator](../general/configuration.md#namespace-separator).
+- Added multiple filters for a route and classname filters
+- Reduced memory usage of `insertBatch()` and `updateBatch()`
+- Added [Session-based CSRF Protection](../libraries/security.md#csrf-protection-methods)
+- Added `valid_url_strict` rule for `Validation`
+- Debug Toolbar
+ - Added formatted query string to timeline
+ - Improved keyword highlighting and escaping of query strings
+
+## Changes
+
+- Always escape identifiers in the `set`, `setUpdateBatch`, and `insertBatch` functions in `BaseBuilder`.
+
+## Deprecations
+
+- Deprecated `CodeIgniter\\Cache\\Handlers\\BaseHandler::RESERVED_CHARACTERS` in favor of the new config property
+
+## Bugs Fixed
+
+See the repo's [CHANGELOG_4.1.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.1.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.1.6.md b/docs/changelogs/v4.1.6.md
new file mode 100644
index 000000000000..c77d3fa6736e
--- /dev/null
+++ b/docs/changelogs/v4.1.6.md
@@ -0,0 +1,48 @@
+# Version 4.1.6
+
+Release Date: January 3, 2022
+
+**4.1.6 release of CodeIgniter4**
+
+## 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
+
+- Multiple table names will no longer be stored in `BaseBuilder::$tableName` - an empty string will be used instead.
+
+### Validation changes
+
+- The previous version of the Validation could not handle an array item. Because of the bug fix, the validation results may be different, or validation may raise a `TypeError`. But the previous version's results are probably incorrect.
+- The Validation separates the validation process of multiple fields like `contacts.*.name` and of a single field. When a single field had array data, the previous version validated each element of the array. The validation rule got an element of the array as the parameter. On the other hand, the current version passes the array to the validation rule as a whole.
+
+## Enhancements
+
+- Full support for PHP 8.1.
+- Database pane on debug toolbar now displays the location where the query was called from. Also displays the full backtrace.
+- [Subqueries](../database/query_builder.md#6-subqueries) in QueryBuilder can now be an instance of the BaseBuilder class.
+- Kint was updated from ^3.3 to ^4.0.
+
+## Changes
+
+- The process of sending cookies has been moved to the `Response` class. Now the `Security` and `CookieStore` classes don't send cookies, but set them on the Response.
+
+## Deprecations
+
+- `Seeder::faker()` and `Seeder::$faker` are deprecated.
+- `BaseBuilder::cleanClone()` is deprecated.
+
+### Sending Cookies
+
+The process of sending cookies has been moved to the `Response` class. And the following methods are deprecated:
+
+- `CookieStore::dispatch()`
+- `CookieStore::setRawCookie()`
+- `CookieStore::setCookie()`
+- `Security::sendCookie()`
+- `Security::doSendCookie()`
+
+## Bugs Fixed
+
+See the repo's [CHANGELOG_4.1.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.1.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.1.7.md b/docs/changelogs/v4.1.7.md
new file mode 100644
index 000000000000..f683b228371d
--- /dev/null
+++ b/docs/changelogs/v4.1.7.md
@@ -0,0 +1,25 @@
+# Version 4.1.7
+
+Release Date: January 9, 2022
+
+**4.1.7 release of CodeIgniter4**
+
+## BREAKING
+
+- Because `FILTER_SANITIZE_STRING` has been deprecated since PHP 8.1, the output of `get_cookie()`, which uses it when `$xssClean` is true, has changed. Now it uses `FILTER_SANITIZE_FULL_SPECIAL_CHARS`. Note that using XSS filtering is a bad practice. It does not prevent XSS attacks perfectly. Using `esc()` with the correct `$context` in the views is recommended.
+
+## Enhancements
+
+None.
+
+## Changes
+
+None.
+
+## Deprecations
+
+None.
+
+## Bugs Fixed
+
+See the repo's [CHANGELOG_4.1.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.1.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.1.8.md b/docs/changelogs/v4.1.8.md
new file mode 100644
index 000000000000..3c2edd52b6d5
--- /dev/null
+++ b/docs/changelogs/v4.1.8.md
@@ -0,0 +1,9 @@
+# Version 4.1.8
+
+Release Date: January 24, 2022
+
+**4.1.8 release of CodeIgniter4**
+
+## 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.
diff --git a/docs/changelogs/v4.1.9.md b/docs/changelogs/v4.1.9.md
new file mode 100644
index 000000000000..d45ebcf4bad3
--- /dev/null
+++ b/docs/changelogs/v4.1.9.md
@@ -0,0 +1,10 @@
+# Version 4.1.9
+
+Release Date: February 25, 2022
+
+**4.1.9 release of CodeIgniter4**
+
+## SECURITY
+
+- *Remote CLI Command Execution Vulnerability* was fixed. See the [Security advisory GHSA-xjp4-6w75-qrj7](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 GHSA-4v37-24gm-h554](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-4v37-24gm-h554) for more information.
diff --git a/docs/changelogs/v4.2.0.md b/docs/changelogs/v4.2.0.md
new file mode 100644
index 000000000000..835c6e99395e
--- /dev/null
+++ b/docs/changelogs/v4.2.0.md
@@ -0,0 +1,143 @@
+# Version 4.2.0
+
+Release Date: June 3, 2022
+
+**4.2.0 release of CodeIgniter4**
+
+## Highlights
+
+- Update minimum PHP requirement to 7.4.
+- To make the default configuration more secure, auto-routing has been changed to disabled by default.
+- **OCI8 Driver for Oracle Database** (*contributed by* [ytetsuro](https://github.com/ytetsuro)). See [Database](#database).
+- **Improved Auto Routing** (opt-in) (*contributed by* [kenjis](https://github.com/kenjis)). See [New Improved Auto Routing](#new-improved-auto-routing).
+- Query Builder **Subqueries** and **UNION** support (*contributed by* [Andrey Pyzhikov](https://github.com/iRedds)). See [Database](#database).
+
+### New Contributors
+
+- [@fdomgjoni99](https://github.com/fdomgjoni99) made their first contribution in [#5608](https://github.com/codeigniter4/CodeIgniter4/pull/5608)
+- [@Nudasoft](https://github.com/Nudasoft) made their first contribution in [#5715](https://github.com/codeigniter4/CodeIgniter4/pull/5715)
+- [@beganovich](https://github.com/beganovich) made their first contribution in [#5823](https://github.com/codeigniter4/CodeIgniter4/pull/5823)
+- [@tcgumus](https://github.com/tcgumus) made their first contribution in [#5851](https://github.com/codeigniter4/CodeIgniter4/pull/5851)
+- [@michaelrk02](https://github.com/michaelrk02) made their first contribution in [#5878](https://github.com/codeigniter4/CodeIgniter4/pull/5878)
+- [@datamweb](https://github.com/datamweb) made their first contribution in [#5894](https://github.com/codeigniter4/CodeIgniter4/pull/5894)
+- [@xlii-chl](https://github.com/xlii-chl) made their first contribution in [#5884](https://github.com/codeigniter4/CodeIgniter4/pull/5884)
+- [@valmorflores](https://github.com/valmorflores) made their first contribution in [#6051](https://github.com/codeigniter4/CodeIgniter4/pull/6051)
+- [@tearoom6](https://github.com/tearoom6) made their first contribution in [#6012](https://github.com/codeigniter4/CodeIgniter4/pull/6012)
+- [@lonnie-vault](https://github.com/lonnie-vault) made their first contribution in [#6060](https://github.com/codeigniter4/CodeIgniter4/pull/6060)
+
+## BREAKING
+
+### Method Signature Changes
+
+- The method signatures of `CodeIgniter\Database\BaseBuilder::join()` and `CodeIgniter\Database\*\Builder::join()` have been changed.
+- The method signature of `Validation::setRule()` has been changed. The `string` typehint on the `$rules` parameter was removed. Extending classes should likewise remove the typehint so as not to break LSP.
+- The method signature of `CodeIgniter\CLI\CommandRunner::_remap()` has been changed to fix a bug.
+- The default parameter values for `Service::reset()` and `CIUnitTestCase::resetServices()` have been changed from `false` to `true`. This is to eliminate unexpected problems during testing, such as `lang()` not getting translated messages.
+
+### Behavior Changes
+
+- The `CodeIgniter\CodeIgniter` class has a new property `$context` and it must have the correct context at runtime. So the following files have been changed:
+ - `public/index.php`
+ - `spark`
+- The `system/bootstrap.php` file has been modified to easily implement [Preloading](https://www.php.net/manual/en/opcache.preloading.php). Returning a `CodeIgniter` instance and loading the `.env` file have been moved to `index.php` and `spark`.
+- The behavior of `CodeIgniter\Autoloader\Autoloader::initialize()` has changed to fix a bug. It used to use the Composer classmap only when `$modules->discoverInComposer` is true. Now it always uses the Composer classmap if Composer is available.
+- The color code output by [CLI::color()](../cli/cli_library.md#color) has been changed to fix a bug.
+- To prevent unexpected access from the web browser, if a controller is added to a cli route (`$routes->cli()`), all methods of that controller are no longer accessible via auto-routing.
+- There is a possible backward compatibility break for users extending the History Collector, and they should probably update the `History::setFiles()` method.
+- The unexpected behavior of `dot_array_search()` has been fixed. Now `dot_array_search('foo.bar.baz', ['foo' => ['bar' => 23]])` returns `null`. Previous versions returned `23`.
+- `CodeIgniter::storePreviousURL()` has been changed to store only the URLs whose Content-Type was `text/html`. It also affects the behavior of `previous_url()` and `redirect()->back()`.
+
+## Enhancements
+
+### New Improved Auto Routing
+
+Added a new, optional, more secure auto router. These are the changes from the legacy auto-routing:
+
+- A controller method needs an HTTP verb prefix like `getIndex()`, `postCreate()`.
+ - Developers always know the HTTP method, so requests by an unexpected HTTP method do not pass.
+- The Default Controller (`Home` by default) and the Default Method (`index` by default) must be omitted in the URI.
+ - It enforces a one-to-one correspondence between controller methods and URIs.
+ - E.g. by default, you can access `/`, but `/home` and `/home/index` will be 404.
+- It checks the method parameter count.
+ - If there are more parameters in the URI than the method parameters, it results in a 404.
+- It does not support the `_remap()` method.
+ - It enforces a one-to-one correspondence between controller methods and URIs.
+- It can't access controllers in Defined Routes.
+ - It completely separates controllers accessible via **Auto Routes** from those accessible via **Defined Routes**.
+
+See [Auto Routing (Improved)](../incoming/auto_routing_improved.md#auto-routing-improved) for the details.
+
+### Database
+
+- Added a new OCI8 database driver.
+ - It can access Oracle Database and supports SQL and PL/SQL statements.
+- QueryBuilder
+ - Added Subqueries in the FROM section. See [Subqueries](../database/query_builder.md#subqueries).
+ - Added Subqueries in the SELECT section. See [Select](../database/query_builder.md#select).
+ - The `BaseBuilder::buildSubquery()` method can take an optional third argument `string $alias`.
+ - Union queries. See [Union queries](../database/query_builder.md#union-queries).
+- Raw SQL string support
+ - Added the class `CodeIgniter\Database\RawSql` which expresses raw SQL strings.
+ - [select()](../database/query_builder.md#rawsql), [where()](../database/query_builder.md#5-rawsql), [like()](../database/query_builder.md#3-rawsql), [join()](../database/query_builder.md#rawsql_1) accept a `CodeIgniter\Database\RawSql` instance.
+ - `DBForge::addField()` default value raw SQL string support. See [Raw Sql Strings as Default Values](../dbmgmt/forge.md#raw-sql-strings-as-default-values).
+- SQLite3 has a new Config item `foreignKeys` that enables foreign key constraints.
+
+### Helpers and Functions
+
+- HTML helper `script_tag()` now uses `null` values to write boolean attributes in minimized form: ``. See the sample code for `script_tag()`.
+- Added a 4th parameter `$includeDir` to `get_filenames()`. See `get_filenames()`.
+- Exception information logged through `log_message()` has been improved. It now includes the file and line where the exception originated. It also does not truncate the message anymore.
+ - The log format has also changed. If users are depending on the log format in their apps, the new log format is "<1-based count> <cleaned filepath>(<line>): <class><function><args>"
+
+### Commands
+
+- Added `spark db:table` command. See [Database Commands](../dbmgmt/db_commands.md) for the details.
+ - You can now see the names of all the tables in the database you are currently connected to in the terminal.
+ - `spark db:table --show`
+ - Or you can see the field names and the records of a table.
+ - `spark db:table my_table`
+ - `spark db:table my_table --limit-rows 50 --limit-field-value 20 --desc`
+ - Or you can see metadata like the column type and max length of a table.
+ - `spark db:table my_table --metadata`
+- The `spark routes` command now shows closure routes, auto routes, and filters. See [URI Routing](../incoming/routing.md#spark-routes).
+
+### Others
+
+- Added `$this->validateData()` in Controller. See [$this->validateData()](../incoming/controllers.md#this-validatedata).
+- Content Security Policy (CSP) enhancements
+ - Added the configs `$scriptNonceTag` and `$styleNonceTag` in `Config\ContentSecurityPolicy` to customize the CSP placeholders (`{csp-script-nonce}` and `{csp-style-nonce}`)
+ - Added the config `$autoNonce` in `Config\ContentSecurityPolicy` to disable the CSP placeholder replacement
+ - Added the functions `csp_script_nonce()` and `csp_style_nonce()` to get nonce attributes
+ - See [Content Security Policy](../outgoing/csp.md#content-security-policy) for details.
+- New [View Decorators](../outgoing/view_decorators.md) allow modifying the generated HTML prior to caching.
+- Added Validation Strict Rules. See [Traditional and Strict Rules](../libraries/validation.md#traditional-and-strict-rules).
+- Added support for webp files to **app/Config/Mimes.php**.
+- `RouteCollection::addRedirect()` can now use placeholders. See [Redirecting Routes](../incoming/routing.md#redirecting-routes) for details.
+- Debugbar enhancements
+ - The Debug Toolbar now uses `microtime()` instead of `time()`.
+- Added a sample file for [Preloading](https://www.php.net/manual/en/opcache.preloading.php). See **preload.php**.
+
+## Changes
+
+- Update minimum PHP requirement to 7.4.
+- To make the default configuration more secure, auto-routing has been changed to disabled by default.
+- Validation. Changed generation of errors when using fields with a wildcard (\*). Now the error key contains the full path. See [Getting All Errors](../libraries/validation.md#getting-all-errors).
+- `Validation::getError()` when using a wildcard will return all found errors matching the mask as a string.
+- The current version of Content Security Policy (CSP) outputs one nonce for script and one for style tags. The previous version outputted one nonce for each tag.
+- The process of sending cookies has been moved to the `Response` class. Now the `Session` class doesn't send cookies, but sets them on the Response.
+
+## Deprecations
+
+- `CodeIgniter\Database\SQLSRV\Connection::getError()` is deprecated. Use `CodeIgniter\Database\SQLSRV\Connection::error()` instead.
+- `CodeIgniter\Debug\Exceptions::cleanPath()` and `CodeIgniter\Debug\Toolbar\Collectors\BaseCollector::cleanPath()` are deprecated. Use the `clean_path()` function instead.
+- `CodeIgniter\Log\Logger::cleanFilenames()` and `CodeIgniter\Test\TestLogger::cleanup()` are both deprecated. Use the `clean_path()` function instead.
+- `CodeIgniter\Router\Router::setDefaultController()` is deprecated.
+- The constant `SPARKED` in **spark** is deprecated. Use the `$context` property in `CodeIgniter\CodeIgniter` instead.
+- `CodeIgniter\Autoloader\Autoloader::discoverComposerNamespaces()` is deprecated, and no longer used.
+- The constants `EVENT_PRIORITY_LOW`, `EVENT_PRIORITY_NORMAL` and `EVENT_PRIORITY_HIGH` are deprecated. Use the class constants `CodeIgniter\Events\Events::PRIORITY_LOW`, `CodeIgniter\Events\Events::PRIORITY_NORMAL` and `CodeIgniter\Events\Events::PRIORITY_HIGH` instead.
+
+## Bugs Fixed
+
+- Fixed a bug where the SQLSRV driver ignored the port value from the config.
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.1.md b/docs/changelogs/v4.2.1.md
new file mode 100644
index 000000000000..e8f819b03fbb
--- /dev/null
+++ b/docs/changelogs/v4.2.1.md
@@ -0,0 +1,16 @@
+# Version 4.2.1
+
+Release Date: June 16, 2022
+
+**4.2.1 release of CodeIgniter4**
+
+## BREAKING
+
+### Behavior Changes
+
+- Guessing the file extension from the MIME type has been changed if the proposed extension is not valid. Previously, the guessing would terminate early and return `null`. Now, if a proposed extension is given and is invalid, the MIME guessing will continue checking using the mapping of extension to MIME types.
+- If there is a cookie with a prefixed name and a cookie with the same name without a prefix, the previous `get_cookie()` had the tricky behavior of returning the cookie without the prefix. This behavior has now been fixed as a bug. See [Upgrading](../installation/upgrade_421.md#get_cookie) for details.
+
+## Bugs Fixed
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.10.md b/docs/changelogs/v4.2.10.md
new file mode 100644
index 000000000000..48215cba9819
--- /dev/null
+++ b/docs/changelogs/v4.2.10.md
@@ -0,0 +1,11 @@
+# Version 4.2.10
+
+Release Date: November 5, 2022
+
+**4.2.10 release of CodeIgniter4**
+
+## Bugs Fixed
+
+- Fixed incorrect PHPDoc types in Session.
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.11.md b/docs/changelogs/v4.2.11.md
new file mode 100644
index 000000000000..8adc20808554
--- /dev/null
+++ b/docs/changelogs/v4.2.11.md
@@ -0,0 +1,26 @@
+# Version 4.2.11
+
+Release Date: December 21, 2022
+
+**4.2.11 release of CodeIgniter4**
+
+## SECURITY
+
+- *Attackers may spoof IP address when using proxy* was fixed. See the [Security advisory GHSA-ghw3-5qvm-3mqc](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-ghw3-5qvm-3mqc) for more information.
+- *Potential Session Handlers Vulnerability* was fixed. See the [Security advisory GHSA-6cq5-8cj7-g558](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-6cq5-8cj7-g558) for more information.
+
+## BREAKING
+
+- The `Config\App::$proxyIPs` value format has been changed. See [Upgrading Guide](../installation/upgrade_4211.md#configappproxyips).
+- The key of the session data record for [DatabaseHandler Driver](../libraries/sessions.md#databasehandler-driver), [MemcachedHandler Driver](../libraries/sessions.md#memcachedhandler-driver) and [RedisHandler Driver](../libraries/sessions.md#redishandler-driver) has changed. See [Upgrading Guide](../installation/upgrade_4211.md#session-handler-key-changes).
+
+## Enhancements
+
+- Full support for PHP 8.2.
+
+## Bugs Fixed
+
+- Fixed a `FileLocator::locateFile()` bug where a similar namespace name could be replaced by another, causing a failure to find a file that exists.
+- Fixed the `RedisHandler` session class to use the correct config when used with a socket connection.
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.12.md b/docs/changelogs/v4.2.12.md
new file mode 100644
index 000000000000..45572156e70b
--- /dev/null
+++ b/docs/changelogs/v4.2.12.md
@@ -0,0 +1,14 @@
+# Version 4.2.12
+
+Release Date: January 9, 2023
+
+**4.2.12 release of CodeIgniter4**
+
+## Bugs Fixed
+
+- Fixed a bug where `Request::getIPaddress()` caused an error on CLI.
+- Fixed `link_tag()` missing `type="application/rss+xml"`.
+- Fixed a bug where `spark migrate:status` showed incorrect filenames when the format was `Y_m_d_His_`.
+- Fixed an error when `Model::save()` saves an object if `$useAutoIncrement` is false.
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.2.md b/docs/changelogs/v4.2.2.md
new file mode 100644
index 000000000000..a5793be7d505
--- /dev/null
+++ b/docs/changelogs/v4.2.2.md
@@ -0,0 +1,35 @@
+# Version 4.2.2
+
+Release Date: August 6, 2022
+
+**4.2.2 release of CodeIgniter4**
+
+## BREAKING
+
+- Now `Services::request()` returns `IncomingRequest` or `CLIRequest`.
+- The method signature of `CodeIgniter\Debug\Exceptions::__construct()` has been changed. The `IncomingRequest` typehint on the `$request` parameter was removed. Extending classes should likewise remove the typehint so as not to break LSP.
+- The method signatures of `BaseBuilder.php::insert()` and `BaseBuilder.php::update()` have been changed. The `?array` typehint on the `$set` parameter was removed.
+- A bug that caused pages to be cached before "after" filters were executed when using page caching has been fixed. Adding response headers or changing the response body in after filters now caches them correctly.
+- Due to a bug fix, now `random_string()` with the first parameter `'crypto'` throws `InvalidArgumentException` if the second parameter `$len` is an odd number.
+
+## Message Changes
+
+- Fixed `invalidRoute` message in `Language/en/HTTP.php`.
+
+## Changes
+
+- Fixed: `BaseBuilder::increment()` and `BaseBuilder::decrement()` do not reset the `BaseBuilder` state after a query.
+- Fixed: Validation of fields with a leading asterisk (wildcard).
+- Now `CLIRequest::isCLI()` always returns true.
+- Now `IncomingRequest::isCLI()` always returns false.
+- **Vagrantfile.dist** has been moved to [CodeIgniter DevKit](https://github.com/codeigniter4/devkit).
+
+## Deprecations
+
+- The parameters of `Services::request()` are deprecated.
+- The first parameter `$cacheConfig` of `CodeIgniter::gatherOutput()` is deprecated.
+- The second parameter `$ifNotExists` of `Forge::_createTable()` is deprecated.
+
+## Bugs Fixed
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.3.md b/docs/changelogs/v4.2.3.md
new file mode 100644
index 000000000000..6c2f58400923
--- /dev/null
+++ b/docs/changelogs/v4.2.3.md
@@ -0,0 +1,13 @@
+# Version 4.2.3
+
+Release Date: August 6, 2022
+
+**4.2.3 release of CodeIgniter4**
+
+## Enhancements
+
+- `Security::generateHash()` is now public and can be used to regenerate the CSRF token manually when `Config\Security::$regenerate` is false.
+
+## Bugs Fixed
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.4.md b/docs/changelogs/v4.2.4.md
new file mode 100644
index 000000000000..bf63c30ca156
--- /dev/null
+++ b/docs/changelogs/v4.2.4.md
@@ -0,0 +1,7 @@
+# Version 4.2.4
+
+Release Date: August 13, 2022
+
+**4.2.4 release of CodeIgniter4**
+
+**Hotfix release to fix download errors; no other content changes.**
diff --git a/docs/changelogs/v4.2.5.md b/docs/changelogs/v4.2.5.md
new file mode 100644
index 000000000000..67cee7e607be
--- /dev/null
+++ b/docs/changelogs/v4.2.5.md
@@ -0,0 +1,21 @@
+# Version 4.2.5
+
+Release Date: August 28, 2022
+
+**4.2.5 release of CodeIgniter4**
+
+## BREAKING
+
+- The method signature of `BaseConnection::tableExists()` has been changed. A second optional parameter `$cached` was added. This controls whether to use cached data. The default is `true` (use cached data).
+- The abstract method signature of `BaseConnection::_listTables()` has been changed. A second optional parameter `$tableName` was added. Providing a table name will generate SQL listing only that table.
+- The method signatures of `Validation::processRules()` and `Validation::getErrorMessage()` have been changed. Both of these methods add a new `$originalField` parameter.
+
+## Enhancements
+
+- Kint has been updated to 4.2.0.
+
+## Bugs Fixed
+
+- When using subqueries in the main query, prefixes are added to the table alias.
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.6.md b/docs/changelogs/v4.2.6.md
new file mode 100644
index 000000000000..d9a103a5207c
--- /dev/null
+++ b/docs/changelogs/v4.2.6.md
@@ -0,0 +1,17 @@
+# Version 4.2.6
+
+Release Date: September 4, 2022
+
+**4.2.6 release of CodeIgniter4**
+
+## Deprecations
+
+- `CodeIgniter\Cookie\Cookie::withNeverExpiring()` is deprecated.
+
+## Bugs Fixed
+
+Many bugs were fixed, but notably:
+
+- AssertionError occurs when using Validation in CLI [#6452](https://github.com/codeigniter4/CodeIgniter4/pull/6452)
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.7.md b/docs/changelogs/v4.2.7.md
new file mode 100644
index 000000000000..474583239d30
--- /dev/null
+++ b/docs/changelogs/v4.2.7.md
@@ -0,0 +1,25 @@
+# Version 4.2.7
+
+Release Date: October 6, 2022
+
+**4.2.7 release of CodeIgniter4**
+
+## SECURITY
+
+- *Secure or HttpOnly flag set in Config\Cookie is not reflected in Cookies issued* was fixed. See the [Security advisory GHSA-745p-r637-7vvp](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-745p-r637-7vvp) for more information.
+- Fixed a bug that prevents CSP headers from being sent when `Config\ContentSecurityPolicy::$autoNonce` is false.
+
+## BREAKING
+
+- The default values of the parameters in `set_cookie()` and `CodeIgniter\HTTP\Response::setCookie()` have been fixed. Now the default values of `$secure` and `$httponly` are `null`, and these values will be replaced with the `Config\Cookie` values.
+- `Time::__toString()` is now locale-independent. It returns database-compatible strings like '2022-09-07 12:00:00' in any locale.
+- The parameters of the Validation rules `Validation\Rule::required_without()` and `Validation\StrictRules\Rule::required_without()` have been changed, and the logic of these rules has also been fixed.
+
+## Message Changes
+
+- Fixed typos in some items in `Language/en/Email.php`.
+- Added missing item `valid_json` in `Language/en/Validation.php`.
+
+## Bugs Fixed
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.8.md b/docs/changelogs/v4.2.8.md
new file mode 100644
index 000000000000..d6a640d7f09f
--- /dev/null
+++ b/docs/changelogs/v4.2.8.md
@@ -0,0 +1,18 @@
+# Version 4.2.8
+
+Release Date: October 30, 2022
+
+**4.2.8 release of CodeIgniter4**
+
+## Deprecations
+
+- The third parameter `$returnResponse` of `CodeIgniter::handleRequest()` is deprecated.
+
+## Bugs Fixed
+
+- Fixed a bug where the `CodeIgniter\HTTP\IncomingRequest::getPostGet()` and `CodeIgniter\HTTP\IncomingRequest::getGetPost()` methods didn't return values from the other stream when `index` was set to `null`.
+- Fixed a bug where `binds` weren't cleaned properly when calling `CodeIgniter\Database\Postgre::replace()` multiple times in the same context.
+- Fixed a bug where `CodeIgniter\Database\SQLSRV\PreparedQuery::_getResult()` was returning a bool value instead of the resource.
+- Fixed a bug in the error handler where, when the callback could not process the error level, it did not pass the error to PHP's standard error handler.
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.2.9.md b/docs/changelogs/v4.2.9.md
new file mode 100644
index 000000000000..b61fc9c1260a
--- /dev/null
+++ b/docs/changelogs/v4.2.9.md
@@ -0,0 +1,13 @@
+# Version 4.2.9
+
+Release Date: October 30, 2022
+
+**4.2.9 release of CodeIgniter4**
+
+**Hotfix release to fix PHPUnit errors in 4.2.8; no other content changes.**
+
+## Bugs Fixed
+
+- Fixed a bug that caused `PHP Fatal error: Trait "Nexus\PHPUnit\Extension\Expeditable" not found` when running PHPUnit.
+
+See the repo's [CHANGELOG_4.2.md](https://github.com/codeigniter4/CodeIgniter4/blob/develop/changelogs/CHANGELOG_4.2.md) for a complete list of bugs fixed.
diff --git a/docs/changelogs/v4.3.0.md b/docs/changelogs/v4.3.0.md
new file mode 100644
index 000000000000..59ef09604f60
--- /dev/null
+++ b/docs/changelogs/v4.3.0.md
@@ -0,0 +1,293 @@
+# Version 4.3.0
+
+Release Date: January 10, 2023
+
+**4.3.0 release of CodeIgniter4**
+
+## Highlights
+
+- Query Builder supports **upsert()**, **upsertBatch()**, **deleteBatch()** and now **\*batch()** methods can set data from a query (*contributed by* [sclubricants](https://github.com/sclubricants)). See [Query Builder](#query-builder).
+- Database Forge supports **adding indexes to existing tables** and **naming indexes** (*contributed by* [sclubricants](https://github.com/sclubricants)). See [Forge](#forge).
+- To make the default configuration more secure, the default Validation Rules have been changed to **Strict Rules**.
+- When a database error occurs, the conditions under which an exception is thrown and the exception classes that can be thrown have been changed. See [Exceptions when Database Errors Occur](#exceptions-when-database-errors-occur).
+
+## BREAKING
+
+### Behavior Changes
+
+#### Exceptions when Database Errors Occur
+
+- The exceptions thrown by the database connection classes have been changed to `CodeIgniter\Database\Exceptions\DatabaseException`. Previously, different database drivers threw different exception classes, but these have been unified into `DatabaseException`.
+- The exceptions thrown by the `execute()` method of Prepared Queries have been changed to `DatabaseException`. Previously, different database drivers might throw different exception classes or not throw exceptions, but these have been unified into `DatabaseException`.
+- During transactions, exceptions are not thrown by default even if `DBDebug` is true.
+- `DBDebug` and `CI_DEBUG` Changes
+ - To be consistent in behavior regardless of environments, `Config\Database::$default['DBDebug']` and `Config\Database::$tests['DBDebug']` have been changed to `true` by default. With these settings, an exception is always thrown when a database error occurs. Previously, it was `false` **only in the production environment**.
+ - Now `DatabaseException` in `BaseBuilder` is thrown if `$DBDebug` is true. Previously, it was thrown if `CI_DEBUG` was true.
+ - The default value of `BaseConnection::$DBDebug` has been changed to `true`.
+ - With these changes, `DBDebug` **now means whether or not to throw an exception when an error occurs**. Although unrelated to debugging, the name has not been changed.
+ - When running transactions with `DBDebug` set to `true`, even if a query error occurs, exceptions are not thrown by default. Previously, if a query error occurred, all the queries would be rolled back, and an exception would be thrown, so [Managing Errors](../database/transactions.md#managing-errors) or [Running Transactions Manually](../database/transactions.md#running-transactions-manually) wouldn't work.
+ - Now when you delete without a WHERE clause in `Model`, `DatabaseException` is thrown even if `CI_DEBUG` is false. Previously, it was thrown if `CI_DEBUG` was true.
+
+#### HTTP Status Code and Exit Code when an Exception Occurs
+
+Previously, CodeIgniter's Exception Handler used the *Exception code* as the *HTTP status code* in some cases, and calculated the *Exit code* based on the Exception code. However, there should be no logical connection between the Exception code and the HTTP status code or Exit code.
+
+- Now the Exception Handler sets the HTTP status code to `500` and sets the Exit code to the constant `EXIT_ERROR` (= `1`) by default.
+- You can change the HTTP status code or Exit code by implementing `HTTPExceptionInterface` or `HasExitCodeInterface` in your Exception class. See [Specify HTTP Status Code in Your Exception](../general/errors.md#specify-http-status-code-in-your-exception) and [Specify Exit Code in Your Exception](../general/errors.md#specify-exit-code-in-your-exception).
+
+For example, the Exit code has changed as follows:
+
+- If an uncaught `ConfigException` occurs, the Exit code is `EXIT_CONFIG` (= `3`) instead of `12`.
+- If an uncaught `CastException` occurs, the Exit code is `EXIT_CONFIG` (= `3`) instead of `9`.
+- If an uncaught `DatabaseException` occurs, the Exit code is `EXIT_DATABASE` (= `8`) instead of `17`.
+
+#### Time
+
+The following methods of the [Time](../libraries/time.md) class had bugs that changed the state of the current object:
+
+- `add()`
+- `modify()`
+- `setDate()`
+- `setISODate()`
+- `setTime()`
+- `sub()`
+
+To fix these bugs, the Time class has been fixed:
+
+- Now the `Time` class extends `DateTimeImmutable` and is completely immutable.
+- The `TimeLegacy` class has been added for backward compatibility, which behaves the same as the unmodified `Time` class.
+
+#### Others
+
+- **Helper:** `script_tag()` and `safe_mailto()` no longer output `type="text/javascript"` in the `
+```
+
+Alternatively, an associative array can be passed to the `script_tag()` function for complete control over all attributes and values:
+
+```php
+ 'js/printer.js', 'defer' => null];
+
+echo script_tag($script);
+//
+```
+
+#### ul($list[, $attributes = ''])
+
+- **Parameters**
+ - **$list** `array` List entries
+ - **$attributes** `array` HTML attributes
+
+- **Returns**: An HTML unordered list element
+
+- **Return type**: `string`
+
+Permits you to generate an unordered HTML list from a simple or multi-dimensional array. Example:
+
+```php
+ 'boldlist',
+ 'id' => 'mylist',
+];
+
+echo ul($list, $attributes);
+```
+
+The above code will produce this:
+
+```html
+
+ red
+ blue
+ green
+ yellow
+
+```
+
+Here is a more complex example, using a multi-dimensional array:
+
+```php
+ 'boldlist',
+ 'id' => 'mylist',
+];
+
+$list = [
+ 'colors' => [
+ 'red',
+ 'blue',
+ 'green',
+ ],
+ 'shapes' => [
+ 'round',
+ 'square',
+ 'circles' => [
+ 'ellipse',
+ 'oval',
+ 'sphere',
+ ],
+ ],
+ 'moods' => [
+ 'happy',
+ 'upset' => [
+ 'defeated' => [
+ 'dejected',
+ 'disheartened',
+ 'depressed',
+ ],
+ 'annoyed',
+ 'cross',
+ 'angry',
+ ],
+ ],
+];
+
+echo ul($list, $attributes);
+```
+
+The above code will produce this:
+
+```html
+
+ colors
+
+
+ shapes
+
+ round
+ square
+ circles
+
+ ellipse
+ oval
+ sphere
+
+
+
+
+ moods
+
+ happy
+ upset
+
+ defeated
+
+ dejected
+ disheartened
+ depressed
+
+
+ annoyed
+ cross
+ angry
+
+
+
+
+
+```
+
+#### ol($list, $attributes = '')
+
+- **Parameters**
+ - **$list** `array` List entries
+ - **$attributes** `array` HTML attributes
+
+- **Returns**: An HTML ordered list element
+
+- **Return type**: `string`
+
+Identical to `ul()`, except that it produces an `` element for ordered lists instead of ``.
+
+#### video($src[, $unsupportedMessage = ''[, $attributes = ''[, $tracks = [][, $indexPage = false]]]])
+
+- **Parameters**
+ - **$src** `mixed` Either a source string or an array of sources. See `source()` function
+ - **$unsupportedMessage** `string` The message to display if the video element is not supported by the browser
+ - **$attributes** `string` HTML attributes
+ - **$tracks** `array` Use the track function inside an array. See `track()` function
+ - **$indexPage** `bool` Should indexPage be added to the video source path
+
+- **Returns**: An HTML video element
+
+- **Return type**: `string`
+
+Permits you to generate an HTML video element from a source string or an array of sources. Example:
+
+```php
+
+ Your browser does not support the video tag.
+
+
+
+
+
+ Your browser does not support the video tag.
+
+
+
+
+
+
+
+
+
+ Your browser does not support the video tag.
+
+```
+
+#### audio($src[, $unsupportedMessage = ''[, $attributes = ''[, $tracks = [][, $indexPage = false]]]])
+
+- **Parameters**
+ - **$src** `mixed` Either a source string or an array of sources. See `source()` function
+ - **$unsupportedMessage** `string` The message to display if the audio element is not supported by the browser
+ - **$attributes** `string` HTML attributes
+ - **$tracks** `array` Use the track function inside an array. See `track()` function
+ - **$indexPage** `bool` Should indexPage be added to the audio source path
+
+- **Returns**: An HTML audio element
+
+- **Return type**: `string`
+
+Identical to `video()`, except that it produces an `` element instead of ``.
+
+#### source($src, $type = 'unknown', $attributes = '', $indexPage = false)
+
+- **Parameters**
+ - **$src** `string` The path of the media resource
+ - **$type** `string` The MIME-type of the resource with optional codecs parameters
+ - **$attributes** `string` HTML attributes
+ - **$indexPage** `bool` Should indexPage be added to the source path
+
+- **Returns**: An HTML source element
+
+- **Return type**: `string`
+
+Lets you create HTML `` elements. The first parameter contains the path of the resource. Example:
+
+```php
+
+```
+
+#### embed($src[, $type = 'unknown'[, $attributes = ''[, $indexPage = false]]])
+
+- **Parameters**
+ - **$src** `string` The path of the resource to embed
+ - **$type** `string` MIME-type
+ - **$attributes** `string` HTML attributes
+ - **$indexPage** `bool` Should indexPage be added to the source path
+
+- **Returns**: An HTML embed element
+
+- **Return type**: `string`
+
+Lets you create HTML `` elements. The first parameter contains the embed source. Example:
+
+```php
+
+```
+
+#### object($data[, $type = 'unknown'[, $attributes = ''[, $params = [][, $indexPage = false]]]])
+
+- **Parameters**
+ - **$data** `string` A resource URL
+ - **$type** `string` Content-type of the resource
+ - **$attributes** `string` HTML attributes
+ - **$indexPage** `bool` Should indexPage be added to the resource URL
+ - **$params** `array` Use the param function inside an array. See `param()` function
+
+- **Returns**: An HTML object element
+
+- **Return type**: `string`
+
+Lets you create HTML `` elements. The first parameter contains the object data. Example:
+
+```php
+
+
+
+
+
+
+```
+
+#### param($name, $value[, $type = 'ref'[, $attributes = '']])
+
+- **Parameters**
+ - **$name** `string` The name of the parameter
+ - **$value** `string` The value of the parameter
+ - **$type** `string` The type of the parameter value
+ - **$attributes** `string` HTML attributes
+
+- **Returns**: An HTML param element
+
+- **Return type**: `string`
+
+Lets you create HTML ` ` elements. The first parameter contains the param source. Example:
+
+```php
+
+```
+
+#### track($src, $kind, $srcLanguage, $label)
+
+- **Parameters**
+ - **$src** `string` The path of the track (.vtt file)
+ - **$kind** `string` The kind of timed track
+ - **$srcLanguage** `string` The language of the timed track
+ - **$label** `string` A user-readable title for the timed track
+
+- **Returns**: An HTML track element
+
+- **Return type**: `string`
+
+Generates a track element to specify timed tracks. The tracks are formatted in WebVTT format. Example:
+
+```php
+
+```
+
+#### doctype([$type = 'html5'])
+
+- **Parameters**
+ - **$type** `string` Doctype name
+
+- **Returns**: An HTML DocType declaration
+
+- **Return type**: `string`
+
+Helps you generate document type declarations (DTDs). HTML 5 is used by default, but many doctypes are available.
+
+Example:
+
+```php
+
+
+echo doctype('html4-trans');
+//
+```
+
+The following is a list of the pre-defined doctypes. These are pulled from **app/Config/DocTypes.php**, or they could be overridden in your **.env** configuration.
+
+| Document type | $type parameter | Result |
+|----|----|----|
+| XHTML 1.1 | xhtml11 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" ""> |
+| XHTML 1.0 Strict | xhtml1-strict | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ""> |
+| XHTML 1.0 Transitional | xhtml1-trans | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ""> |
+| XHTML 1.0 Frameset | xhtml1-frame | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" ""> |
+| XHTML Basic 1.1 | xhtml-basic11 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" ""> |
+| HTML 5 | html5 | <!DOCTYPE html> |
+| HTML 4 Strict | html4-strict | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" ""> |
+| HTML 4 Transitional | html4-trans | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" ""> |
+| HTML 4 Frameset | html4-frame | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" ""> |
+| MathML 1.01 | mathml1 | <!DOCTYPE math SYSTEM ""> |
+| MathML 2.0 | mathml2 | <!DOCTYPE math PUBLIC "-//W3C//DTD MathML 2.0//EN" ""> |
+| SVG 1.0 | svg10 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" ""> |
+| SVG 1.1 Full | svg11 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ""> |
+| SVG 1.1 Basic | svg11-basic | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" ""> |
+| SVG 1.1 Tiny | svg11-tiny | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" ""> |
+| XHTML+MathML+SVG (XHTML host) | xhtml-math-svg-xh | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" ""> |
+| XHTML+MathML+SVG (SVG host) | xhtml-math-svg-sh | <!DOCTYPE svg:svg PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" ""> |
+| XHTML+RDFa 1.0 | xhtml-rdfa-1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" ""> |
+| XHTML+RDFa 1.1 | xhtml-rdfa-2 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.1//EN" ""> |
diff --git a/docs/helpers/index.md b/docs/helpers/index.md
new file mode 100644
index 000000000000..75e540b16eed
--- /dev/null
+++ b/docs/helpers/index.md
@@ -0,0 +1,17 @@
+# Helpers
+
+Helpers are collections of useful procedural functions. See also [Helper Functions](../general/helpers.md).
+
+- [Array Helper](array_helper.md)
+- [Cookie Helper](cookie_helper.md)
+- [Date Helper](date_helper.md)
+- [Filesystem Helper](filesystem_helper.md)
+- [Form Helper](form_helper.md)
+- [HTML Helper](html_helper.md)
+- [Inflector Helper](inflector_helper.md)
+- [Number Helper](number_helper.md)
+- [Security Helper](security_helper.md)
+- [Test Helper](test_helper.md)
+- [Text Helper](text_helper.md)
+- [URL Helper](url_helper.md)
+- [XML Helper](xml_helper.md)
diff --git a/docs/helpers/inflector_helper.md b/docs/helpers/inflector_helper.md
new file mode 100644
index 000000000000..5c7394a74194
--- /dev/null
+++ b/docs/helpers/inflector_helper.md
@@ -0,0 +1,233 @@
+# Inflector Helper
+
+The Inflector Helper file contains functions that permit you to change **English** words to plural, singular, camel case, etc.
+
+## Loading this Helper
+
+This helper is loaded using the following code:
+
+```php
+/Number.php*
+
+#### number_to_amount($num[, $precision = 0[, $locale = null]])
+
+- **Parameters**
+ - **$num** `mixed` Number to format
+ - **$precision** `int` Floating point precision
+ - **$locale** `string|null` The locale to use for formatting
+
+- **Returns**: A human-readable version of the string, or false if the provided value is not numeric
+
+- **Return type**: `string|false`
+
+Converts a number into a human-readable version, like **123.4 trillion** for numbers up to the quadrillions. Examples:
+
+```php
+assertTrue($this->userHasAccess($user));
+ }
+}
+```
diff --git a/docs/helpers/text_helper.md b/docs/helpers/text_helper.md
new file mode 100644
index 000000000000..826b87188e8c
--- /dev/null
+++ b/docs/helpers/text_helper.md
@@ -0,0 +1,517 @@
+# Text Helper
+
+The Text Helper file contains functions that assist in working with text.
+
+## Loading this Helper
+
+This helper is loaded using the following code:
+
+```php
+ "Is your name O\\'reilly?",
+ 'answer' => "No, my name is O\\'connor.",
+];
+
+$str = strip_slashes($str);
+```
+
+The above will return the following array:
+
+```php
+ "Is your name O'reilly?",
+ 'answer' => "No, my name is O'connor.",
+];
+```
+
+!!! note "Note"
+ For historical reasons, this function will also accept and handle string inputs. However, this makes it just an alias for `stripslashes()`.
+
+#### reduce_multiples($str[, $character = ','[, $trim = false]])
+
+- **Parameters**
+ - **$str** `string` Text to search in
+ - **$character** `string` Character to reduce
+ - **$trim** `bool` Whether to also trim the specified character
+
+- **Returns**: Reduced string
+
+- **Return type**: `string`
+
+Reduces multiple instances of a particular character occurring directly after each other. Example:
+
+```php
+'[, $tag_close = '</mark>']])
+
+- **Parameters**
+ - **$str** `string` Input string
+ - **$phrase** `string` Phrase to highlight
+ - **$tag_open** `string` Opening tag used for the highlight
+ - **$tag_close** `string` Closing tag for the highlight
+
+- **Returns**: String with a phrase highlighted via HTML
+
+- **Return type**: `string`
+
+Will highlight a phrase within a text string. The first parameter will contain the original string, and the second will contain the phrase you wish to highlight. The third and fourth parameters will contain the opening/closing HTML tags you would like the phrase wrapped in.
+
+Example:
+
+```php
+', '');
+```
+
+The above code prints:
+
+```
+Here is a nice text string about nothing in particular.
+```
+
+!!! note "Note"
+ This function used to use the `` tag by default. Older browsers might not support the new HTML5 mark tag, so it is recommended that you insert the following CSS code into your stylesheet if you need to support such browsers:
+
+ ```
+ mark {
+ background: #ff0;
+ color: #000;
+ }
+ ```
+
+#### word_wrap($str[, $charlim = 76])
+
+- **Parameters**
+ - **$str** `string` Input string
+ - **$charlim** `int` Character limit
+
+- **Returns**: Word-wrapped string
+
+- **Return type**: `string`
+
+Wraps text at the specified *character* count while maintaining complete words.
+
+Example:
+
+```php
+My News
+
+echo anchor('news/local/123', 'My News', ['title' => 'The best news!']);
+// Prints: My News
+
+echo anchor('', 'Click here');
+// Prints: Click here
+```
+
+As above, you may specify an alternate configuration. You may find the alternate configuration useful if generating links for a different site than yours, which contains different configuration preferences. We use this for unit testing the framework itself.
+
+!!! note "Note"
+ Attributes passed into the anchor function are automatically escaped to protect against XSS attacks.
+
+#### anchor_popup([$uri = ''[, $title = ''[, $attributes = false[, $altConfig = null]]]])
+
+- **Parameters**
+ - **$uri** `string` URI string
+ - **$title** `string` Anchor title
+ - **$attributes** `array|false|object|string` HTML attributes
+ - \Config\App `$altConfig` Alternate configuration to use
+
+- **Returns**: Pop-up hyperlink
+
+- **Return type**: `string`
+
+Nearly identical to the `anchor()` function except that it opens the URL in a new window. You can specify JavaScript window attributes in the third parameter to control how the window is opened. If the third parameter is not set, it will simply open a new window with your own browser settings.
+
+Here is an example with attributes:
+
+```php
+ 800,
+ 'height' => 600,
+ 'scrollbars' => 'yes',
+ 'status' => 'yes',
+ 'resizable' => 'yes',
+ 'screenx' => 0,
+ 'screeny' => 0,
+ 'window_name' => '_blank',
+];
+
+echo anchor_popup('news/local/123', 'Click Me!', $atts);
+```
+
+As above, you may specify an alternate configuration. You may find the alternate configuration useful if generating links for a different site than yours, which contains different configuration preferences. We use this for unit testing the framework itself.
+
+!!! note "Note"
+ The above attributes are the function defaults, so you only need to set the ones that are different from what you need. If you want the function to use all of its defaults, simply pass an empty array in the third parameter:
+
+ ```php
+ 'Mail me'];
+echo mailto('me@my-site.com', 'Contact Me', $attributes);
+```
+
+!!! note "Note"
+ Attributes passed into the mailto function are automatically escaped to protect against XSS attacks.
+
+#### safe_mailto($email[, $title = ''[, $attributes = '']])
+
+- **Parameters**
+ - **$email** `string` E-mail address
+ - **$title** `string` Anchor title
+ - **$attributes** `array|object|string` HTML attributes
+
+- **Returns**: A spam-safe "mail to" hyperlink
+
+- **Return type**: `string`
+
+Identical to the `mailto()` function except it writes an obfuscated version of the *mailto* tag using ordinal numbers written with JavaScript to help prevent the e-mail address from being harvested by spam bots.
+
+#### auto_link($str[, $type = 'both'[, $popup = false]])
+
+- **Parameters**
+ - **$str** `string` Input string
+ - **$type** `string` Link type (`'email'`, `'url'` or `'both'`)
+ - **$popup** `bool` Whether to create popup links
+
+- **Returns**: Linkified string
+
+- **Return type**: `string`
+
+Automatically turns URLs and e-mail addresses contained in a string into links. Example:
+
+```php
+get('/', 'Home::index');
+
+?>
+
+Home
+
+```
+
+You can also add arguments to the route. Here is an example:
+
+```php
+get('pages/(:segment)', 'Page::index/$1');
+
+?>
+
+Home
+
+```
+
+This is useful because you can still change your routes after putting links into your views.
+
+Since v4.3.0, when you use `{locale}` in your route, you can optionally specify the locale value as the last parameter.
+
+```php
+add(
+ '{locale}/users/(:num)/gallery/(:num)',
+ 'Galleries::showUserGallery/$1/$2',
+ ['as' => 'user_gallery'],
+);
+
+?>
+
+View Gallery
+
+```
+
+For full details, see [Reverse Routing](../incoming/routing.md#reverse-routing) and [Named Routes](../incoming/routing.md#named-routes).
+
+#### url_is($path)
+
+- **Parameters**
+ - **$path** `string` The URL path relative to baseURL to check the current URI path against.
+
+- **Return type**: `boolean`
+
+Compares the current URL's path against the given path to see if they match. Example:
+
+```php
+
+- Single and double quotes: ' "
+- Dashes: -
+
+This function ignores ampersands if they are part of existing numbered character entities, e.g., {. Example:
+
+```php
+Here is a paragraph & an entity ({).';
+$string = xml_convert($string);
+echo $string;
+```
+
+Outputs:
+
+```html
+<p>Here is a paragraph & an entity ({).</p>
+```
diff --git a/docs/images/debugbar.png b/docs/images/debugbar.png
new file mode 100644
index 000000000000..1661e893aa3d
Binary files /dev/null and b/docs/images/debugbar.png differ
diff --git a/docs/images/error.png b/docs/images/error.png
new file mode 100644
index 000000000000..14fac852d48f
Binary files /dev/null and b/docs/images/error.png differ
diff --git a/docs/images/tutorial1.png b/docs/images/tutorial1.png
new file mode 100644
index 000000000000..9ec9abf9a6fa
Binary files /dev/null and b/docs/images/tutorial1.png differ
diff --git a/docs/images/tutorial2.png b/docs/images/tutorial2.png
new file mode 100644
index 000000000000..a65e0e0885b3
Binary files /dev/null and b/docs/images/tutorial2.png differ
diff --git a/docs/images/tutorial3.png b/docs/images/tutorial3.png
new file mode 100644
index 000000000000..16f557595d04
Binary files /dev/null and b/docs/images/tutorial3.png differ
diff --git a/docs/images/tutorial4.png b/docs/images/tutorial4.png
new file mode 100644
index 000000000000..33c33ae2615c
Binary files /dev/null and b/docs/images/tutorial4.png differ
diff --git a/docs/images/welcome.png b/docs/images/welcome.png
new file mode 100644
index 000000000000..d3a2b3c38377
Binary files /dev/null and b/docs/images/welcome.png differ
diff --git a/docs/incoming/auto_routing_improved.md b/docs/incoming/auto_routing_improved.md
new file mode 100644
index 000000000000..f3bc41acf561
--- /dev/null
+++ b/docs/incoming/auto_routing_improved.md
@@ -0,0 +1,472 @@
+# Auto Routing (Improved)
+
+!!! success "Available from version 4.2.0"
+
+## What is Auto Routing (Improved)?
+
+By default, all routes must be [defined](routing.md#setting-routing-rules) in the configuration file.
+
+However, with **Auto Routing (Improved)**, you can define the controller name and its method name according to the convention and it will be automatically routed. In other words, there is no need to define routes manually.
+
+If you enable Auto Routing (Improved), when no defined route is found that matches the URI, the system will attempt to match that URI against the controllers and methods.
+
+!!! important "Important"
+ For security reasons, if a controller is used in the defined routes, Auto Routing (Improved) does not route to the controller.
+
+!!! note "Note"
+ Auto Routing (Improved) is disabled by default. To use it, see [Enable Auto Routing (Improved)](#enable-auto-routing-improved).
+
+## Differences from Auto Routing (Legacy)
+
+[Auto Routing (Legacy)](routing.md#auto-routing-legacy) is a routing system from CodeIgniter 3. If you are not familiar with it, go to the next section.
+
+If you know it well, these are some changes in **Auto Routing (Improved)**:
+
+- A controller method needs an HTTP verb prefix like `getIndex()`, `postCreate()`.
+ - Since developers always know the HTTP method, a request with an unexpected HTTP method will never execute the controller.
+- The Default Controller (`Home` by default) and the Default Method (`index` by default) must be omitted in the URI.
+ - It restricts one-to-one correspondence between controller methods and URIs.
+ - For example, by default, you can access `/`, but `/home` and `/home/index` will be 404 Not Found.
+- It checks the method parameter count.
+ - If there are more parameters in the URI than the method parameters, it results in 404 Not Found.
+- It does not support the `_remap()` method.
+ - It restricts one-to-one correspondence between controller methods and URIs.
+ - But it has the [Default Method Fallback](#default-method-fallback) feature instead.
+- It cannot access controllers in Defined Routes.
+ - It completely separates controllers accessible via **Auto Routing** from those accessible via **Defined Routes**.
+
+## Enable Auto Routing (Improved)
+
+To use it, you need to set the `$autoRoute` option to `true` in **app/Config/Routing.php**:
+
+```
+public bool $autoRoute = true;
+```
+
+And you need to change the property `$autoRoutesImproved` to `true` in **app/Config/Feature.php**:
+
+```
+public bool $autoRoutesImproved = true;
+```
+
+!!! important "Important"
+ When you use Auto Routing (Improved), you must remove the line `$routes->get('/', 'Home::index');` in **app/Config/Routes.php**, because defined routes take precedence over Auto Routing, and controllers defined in the defined routes are denied access by Auto Routing (Improved) for security reasons.
+
+## URI Segments
+
+The segments in the URL, following the Model-View-Controller approach, usually represent:
+
+```
+http://example.com/{class}/{method}/{param1}
+```
+
+1. The first segment represents the controller **class** that should be invoked.
+2. The second segment represents the class **method** that should be called.
+3. The third, and any additional segments, represent any **parameters** that will be passed to the controller method.
+
+Consider this URI:
+
+```
+http://example.com/hello-world/hello/1
+```
+
+In the above example, when you send an HTTP request with the **GET** method, Auto Routing (Improved) would attempt to find a controller named `App\Controllers\HelloWorld` and execute the `getHello()` method, passing `'1'` as the first parameter.
+
+!!! note "Note"
+ A controller method that will be executed by Auto Routing (Improved) needs an HTTP verb (`get`, `post`, `put`, etc.) prefix like `getIndex()`, `postCreate()`.
+
+!!! note "Note"
+ When a controller's short name matches the first segment of a URI, it will be loaded.
+
+## Let's try it: Hello World!
+
+Let's create a simple controller so you can see it in action.
+
+### Create a Controller
+
+Using your text editor, create a file called **HelloWorld.php** in your **app/Controllers** directory, and put the following code in it.
+
+```php
+ 'Acme\Blog\Controllers',
+];
+```
+
+The key is the first URI segment for the module, and the value is the controller namespace. In the above configuration, **http://localhost:8080/blog/foo/bar** will be routed to `Acme\Blog\Controllers\Foo::getBar()`.
+
+!!! note "Note"
+ If you define `$moduleRoutes`, the routing for the module takes precedence. In the above example, even if you have the `App\Controllers\Blog` controller, **http://localhost:8080/blog** will be routed to the default controller `Acme\Blog\Controllers\Home`.
diff --git a/docs/incoming/content_negotiation.md b/docs/incoming/content_negotiation.md
new file mode 100644
index 000000000000..95a2989f505e
--- /dev/null
+++ b/docs/incoming/content_negotiation.md
@@ -0,0 +1,188 @@
+# Content Negotiation
+
+## What is Content Negotiation?
+
+Content negotiation is a way to determine what type of content to return to the client based on what the client can handle, and what the server can handle. This can be used to determine whether the client wants HTML or JSON returned, whether the image should be returned as a JPEG or PNG, what type of compression is supported and more. This is done by analyzing four different headers which can each support multiple value options, each with its own priority.
+
+Trying to match this up manually can be pretty challenging. CodeIgniter provides the `Negotiator` class that can handle this for you.
+
+At its heart, Content Negotiation is simply a part of the HTTP specification that allows a single resource to serve more than one type of content, allowing the clients to request the type of data that works best for them.
+
+A classic example of this is a browser that cannot display PNG files and requests only GIF or JPEG images. When the server receives the request, it looks at the available file types the client is requesting and selects the best match from the image formats that it supports, in this case likely choosing a JPEG image to return.
+
+This same negotiation can happen with four types of data:
+
+- **Media/Document Type** - This could be image format, or HTML vs. XML or JSON.
+- **Character Set** - The character set the returned document should be set in. It is typically UTF-8.
+- **Document Encoding** - Typically the type of compression used on the results.
+- **Document Language** - For sites that support multiple languages, this helps determine which to return.
+
+## Loading the Class
+
+You can load an instance of the class manually through the Service class:
+
+```php
+negotiate('media', ['foo', 'bar']);
+```
+
+When accessed this way, the first parameter is the type of content you're trying to find a match for, while the second is an array of supported values.
+
+## Negotiating
+
+In this section, we will discuss the 4 types of content that can be negotiated and show how that would look using both of the methods described above to access the negotiator.
+
+### Media
+
+The first aspect to look at is handling 'media' negotiations. These are provided by the `Accept` header, which is one of the most complex headers available. A common example is the client telling the server what format it wants the data in. This is especially common in APIs. For example, a client might request JSON-formatted data from an API endpoint:
+
+```
+GET /foo HTTP/1.1
+Accept: application/json
+```
+
+The server now needs to provide a list of what types of content it can provide. In this example, the API might be able to return data as raw HTML, JSON, or XML. This list should be provided in order of preference:
+
+```php
+negotiate('media', $supported);
+// or
+$format = $negotiate->media($supported);
+```
+
+In this case, both the client and the server can agree on formatting the data as JSON, so 'json' is returned from the negotiate method. By default, if no match is found, the first element in the `$supported` array would be returned. In some cases, though, you might need to enforce the format to be a strict match. If you pass `true` as the final value, it will return an empty string if no match is found:
+
+```php
+negotiate('media', $supported, true);
+// or
+$format = $negotiate->media($supported, true);
+```
+
+### Language
+
+Another common usage is to determine the language the content should be served in. If you are running only a single-language site, this obviously isn't going to make much difference, but any site that can offer up multiple translations of content will find this useful, since the browser will typically send the preferred language along in the `Accept-Language` header:
+
+```
+GET /foo HTTP/1.1
+Accept-Language: fr; q=1.0, en; q=0.5
+```
+
+In this example, the browser would prefer French, with a second choice of English. If your website supports English and German, you would do something like:
+
+```php
+negotiate('language', $supported);
+// or
+$lang = $negotiate->language($supported);
+```
+
+In this example, 'en' would be returned as the current language. If no match is found, it will return the first element in the `$supported` array, so that should always be the preferred language.
+
+#### Strict Locale Negotiation
+
+!!! success "Available from version 4.6.0"
+
+By default, the locale is determined on a lossy comparison basis. So only the first part of the locale string is taken into account (language). This is usually sufficient. But sometimes we want to be able to distinguish between regional versions such as `en-US` and `en-GB` to serve different content.
+
+For such cases, we have introduced a new setting that can be enabled via `Config\Feature::$strictLocaleNegotiation`. This will ensure that a strict comparison is made first.
+
+!!! note "Note"
+ CodeIgniter comes with translations only for primary language tags ('en', 'fr', etc.). So if you enable this feature and your settings in `Config\App::$supportedLocales` include regional language tags ('en-US', 'fr-FR', etc.), then keep in mind that if you have your own translation files, you **must also change** the folder names for CodeIgniter's translation files to match what you put in the `$supportedLocales` array.
+
+Now let's consider the below example. The browser's preferred language will be set as this:
+
+```
+GET /foo HTTP/1.1
+Accept-Language: fr; q=1.0, en-GB; q=0.5
+```
+
+In this example, the browser would prefer French, with a second choice of English (United Kingdom). Your website, on the other hand, supports German and English (United States):
+
+```php
+negotiate('language', $supported);
+// or
+$lang = $negotiate->language($supported);
+```
+
+In this example, 'en-US' would be returned as the current language. If no match is found, it will return the first element in the `$supported` array. Here is exactly how the locale selection process works.
+
+Even though 'fr' is preferred by the browser, it is not in our `$supported` array. The same problem occurs with 'en-GB', but here we will be able to search for variants. First, we will fall back to the most general locale (in this case 'en') which again is not in our array. Then we will search for the regional locale 'en-'. And that's when our value from the `$supported` array will be matched. We will return 'en-US'.
+
+So the process of selecting a locale is as follows:
+
+1. strict match ('en-GB') - ISO 639-1 plus ISO 3166-1 alpha-2
+2. general locale match ('en') - ISO 639-1
+3. regional locale match ('en-') - ISO 639-1 plus "wildcard" for ISO 3166-1 alpha-2
+
+### Encoding
+
+The `Accept-Encoding` header contains the content encodings the client prefers to receive, and is used to specify the type of compression the client supports:
+
+```
+GET /foo HTTP/1.1
+Accept-Encoding: compress, gzip
+```
+
+Your web server will define what types of compression you can use. Some, like Apache, only support **gzip**:
+
+```php
+negotiate('encoding', ['gzip']);
+// or
+$type = $negotiate->encoding(['gzip']);
+```
+
+See more at [Wikipedia](https://en.wikipedia.org/wiki/HTTP_compression).
+
+### Character Set
+
+The desired character set is passed through the `Accept-Charset` header:
+
+```
+GET /foo HTTP/1.1
+Accept-Charset: utf-16, utf-8
+```
+
+By default, if no matches are found, **utf-8** will be returned:
+
+```php
+negotiate('charset', ['utf-8']);
+// or
+$charset = $negotiate->charset(['utf-8']);
+```
diff --git a/docs/incoming/controller_attributes.md b/docs/incoming/controller_attributes.md
new file mode 100644
index 000000000000..aecc5ed8ce9f
--- /dev/null
+++ b/docs/incoming/controller_attributes.md
@@ -0,0 +1,274 @@
+# Controller Attributes
+
+PHP Attributes can be used to define filters and other metadata on controller classes and methods. This keeps the configuration close to the code it affects, and can make it easier to see at a glance what filters are applied to a given controller or method. This works across all routing methods, including auto-routing, which allows for near feature parity between the more robust route declarations and auto-routing.
+
+## Getting Started
+
+Controller Attributes can be applied to either the entire class or a specific method. The following example shows how to apply the `Filter` attribute to a controller class:
+
+```php
+setHeader($this->name, $this->value);
+
+ return $response;
+ }
+}
+```
+
+You can then apply this custom attribute to a controller class or method just like the built-in attributes:
+
+```php
+response->setJSON([
+ 'name' => 'John Doe',
+ 'email' => 'john@example.com',
+ ]);
+ }
+
+ /**
+ * Add multiple custom headers using the IS_REPEATABLE attribute option.
+ * Each AddHeader attribute will be executed in order.
+ */
+ #[AddHeader('X-API-Version', '2.0')]
+ #[AddHeader('X-Rate-Limit', '100')]
+ #[AddHeader('X-Content-Source', 'cache')]
+ public function statistics()
+ {
+ return $this->response->setJSON([
+ 'users' => 1500,
+ 'posts' => 3200,
+ ]);
+ }
+
+ /**
+ * Combine custom attributes with built-in attributes.
+ * The Cache attribute will cache the response,
+ * and AddHeader will add the custom header.
+ */
+ #[AddHeader('X-Powered-By', 'My Custom API')]
+ #[Cache(for: 3600)]
+ public function dashboard()
+ {
+ return $this->response->setJSON([
+ 'status' => 'operational',
+ 'uptime' => '99.9%',
+ ]);
+ }
+}
+```
diff --git a/docs/incoming/controllers.md b/docs/incoming/controllers.md
new file mode 100644
index 000000000000..4e5404a75c71
--- /dev/null
+++ b/docs/incoming/controllers.md
@@ -0,0 +1,582 @@
+# Controllers
+
+Controllers are the heart of your application, as they determine how HTTP requests should be handled.
+
+## What is a Controller?
+
+A Controller is simply a class file that handles an HTTP request. [URI Routing](routing.md) associates a URI with a controller. It returns a view string or `Response` object.
+
+Every controller you create should extend the `BaseController` class. This class provides several features that are available to all of your controllers.
+
+## Constructor
+
+CodeIgniter's Controller has a special constructor `initController()`. It will be called by the framework after PHP's constructor `__construct()` execution.
+
+If you want to override `initController()`, don't forget to add `parent::initController($request, $response, $logger);` in the method:
+
+```php
+to('route');` does not work.
+
+The `initController()` method sets the following three properties.
+
+## Included Properties
+
+CodeIgniter's Controller provides these properties.
+
+### Request Object
+
+The application's main [Request Instance](incomingrequest.md) is always available as a class property, `$this->request`.
+
+### Response Object
+
+The application's main [Response Instance](../outgoing/response.md) is always available as a class property, `$this->response`.
+
+### Logger Object
+
+An instance of the [Logger](../general/logging.md) class is available as a class property, `$this->logger`.
+
+### Helpers
+
+You can define an array of helper files as a class property. Whenever the controller is loaded, these helper files will be automatically loaded into memory so that you can use their functions anywhere inside the controller:
+
+```php
+request->isSecure()) {
+ $this->forceHTTPS();
+}
+```
+
+By default, and in modern browsers that support the HTTP Strict Transport Security header, this call should force the browser to convert non-HTTPS calls to HTTPS calls for one year. You can modify this by passing the duration (in seconds) as the first parameter:
+
+```php
+request->isSecure()) {
+ $this->forceHTTPS(31536000); // one year
+}
+```
+
+!!! note "Note"
+ A number of [time-based constants](../general/common_functions.md) are always available for you to use, including `YEAR`, `MONTH`, and more.
+
+## Validating Data
+
+### $this->validateData()
+
+!!! success "Available from version 4.2.0"
+
+To simplify data checking, the controller also provides the convenience method `validateData()`.
+
+The method accepts (1) an array of data to validate, (2) an array of rules, (3) an optional array of custom error messages to display if the items are not valid, and (4) an optional database group to use.
+
+The [Validation Library docs](../libraries/validation.md) have details on rule and message array formats, as well as available rules:
+
+```php
+ $id,
+ 'name' => $this->request->getPost('name'),
+ ];
+
+ $rule = [
+ 'id' => 'integer',
+ 'name' => 'required|max_length[255]',
+ ];
+
+ if (! $this->validateData($data, $rule)) {
+ return view('store/product', [
+ 'errors' => $this->validator->getErrors(),
+ ]);
+ }
+
+ // ...
+ }
+}
+```
+
+### $this->validate()
+
+!!! important "Important"
+ This method exists only for backward compatibility. Do not use it in new projects. Even if you are already using it, we recommend that you use the `validateData()` method instead.
+
+The controller also provides the convenience method `validate()`.
+
+!!! warning "Warning"
+ Instead of `validate()`, use `validateData()` to validate POST data only. `validate()` uses `$request->getVar()` which returns `$_GET`, `$_POST` or `$_COOKIE` data in that order (depending on **php.ini** [request-order](https://www.php.net/manual/en/ini.core.php#ini.request-order)). Newer values override older values. POST values may be overridden by the cookies if they have the same name.
+
+The method accepts an array of rules in the first parameter, and in the optional second parameter, an array of custom error messages to display if the items are not valid.
+
+Internally, this uses the controller's `$this->request` instance to get the data to be validated.
+
+The [Validation Library docs](../libraries/validation.md) have details on rule and message array formats, as well as available rules:
+
+```php
+validate([
+ 'email' => "required|is_unique[users.email,id,{$userID}]",
+ 'name' => 'required|alpha_numeric_spaces',
+ ])) {
+ // The validation failed.
+ return view('users/update', [
+ 'errors' => $this->validator->getErrors(),
+ ]);
+ }
+
+ // The validation was successful.
+
+ // Get the validated data.
+ $validData = $this->validator->getValidated();
+
+ // ...
+ }
+}
+```
+
+!!! warning "Warning"
+ When you use the `validate()` method, you should use the [getValidated()](../libraries/validation.md#getting-validated-data) method to get the validated data, because the `validate()` method uses the [Validation::withRequest()](../libraries/validation.md#withrequest) method internally, and it validates data from [$request->getJSON()](incomingrequest.md#getting-json-data) or [$request->getRawInput()](incomingrequest.md#retrieving-raw-data-put-patch-delete) or [$request->getVar()](incomingrequest.md#getting-data), and an attacker could change what data is validated.
+
+!!! note "Note"
+ The [$this->validator->getValidated()](../libraries/validation.md#getting-validated-data) method can be used since v4.4.0.
+
+If you find it simpler to keep the rules in the configuration file, you can replace the `$rules` array with the name of the group as defined in **app/Config/Validation.php**:
+
+```php
+validate('userRules')) {
+ // The validation failed.
+ return view('users/update', [
+ 'errors' => $this->validator->getErrors(),
+ ]);
+ }
+
+ // The validation was successful.
+
+ // Get the validated data.
+ $validData = $this->validator->getValidated();
+
+ // ...
+ }
+}
+```
+
+!!! note "Note"
+ Validation can also be handled automatically in the model, but sometimes it's easier to do it in the controller. The choice is up to you.
+
+## Protecting Methods
+
+In some cases, you may want certain methods hidden from public access. To achieve this, simply declare the method as `private` or `protected`. That will prevent it from being served by a URL request.
+
+For example, if you were to define a method like this for the `Helloworld` controller:
+
+```php
+(\)*\
+ */
+
+ $routes->get('helloworld', '\App\Controllers\HelloWorld::index');
+ ```
+
+### Methods (Legacy)
+
+In the above example, the method name is `index()`. The `index()` method is always loaded by default if the **second segment** of the URI is empty. Another way to show your "Hello World" message would be this:
+
+```
+example.com/index.php/helloworld/index/
+```
+
+**The second segment of the URI determines which method in the controller gets called.**
+
+Let's try it. Add a new method to your controller:
+
+```php
+get('/', 'Home::index');
+```
+
+If you now browse to your site without specifying any URI segments, you'll see the "Hello World" message.
+
+!!! note "Note"
+ The line `$routes->get('/', 'Home::index');` is an optimization that you will want to use in a "real-world" app. But for demonstration purposes we don't want to use that feature. `$routes->get()` is explained in [URI Routing](routing.md).
+
+For more information, please refer to the [Configuration Options (Legacy)](routing.md#configuration-options-legacy) documentation.
+
+### Organizing Your Controllers into Sub-directories (Legacy)
+
+If you are building a large application, you might want to hierarchically organize or structure your controllers into sub-directories. CodeIgniter permits you to do this.
+
+Simply create sub-directories under the main **app/Controllers**, and place your controller classes within them.
+
+!!! important "Important"
+ Directory names MUST start with an uppercase letter and ONLY the first character can be uppercase.
+
+When using this feature, the first segment of your URI must specify the directory. For example, let's say you have a controller located here:
+
+```
+app/Controllers/Products/Shoes.php
+```
+
+To call the above controller, your URI will look something like this:
+
+```
+example.com/index.php/products/shoes/show/123
+```
+
+!!! note "Note"
+ You cannot have directories with the same name in **app/Controllers** and **public/**. This is because if there is a directory, the web server will search for it and it will not be routed to CodeIgniter.
+
+Each of your sub-directories may contain a default controller which will be called if the URL contains *only* the sub-directory. Simply put a controller in there that matches the name of your default controller as specified in your **app/Config/Routing.php** file.
+
+CodeIgniter also permits you to map your URIs using its [Defined Route Routing](routing.md#setting-routing-rules).
+
+## Remapping Method Calls
+
+!!! note "Note"
+ **Auto Routing (Improved)** intentionally does not support this feature.
+
+As noted above, the second segment of the URI typically determines which method in the controller gets called. CodeIgniter permits you to override this behavior through the use of the `_remap()` method:
+
+```php
+{$method}();
+ }
+
+ return $this->default_method();
+ }
+}
+```
+
+Any extra segments after the method name are passed into `_remap()`. These parameters can be passed to the method to emulate CodeIgniter's default behavior.
+
+Example:
+
+```php
+{$method}(...$params);
+ }
+
+ throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
+ }
+}
+```
+
+## Extending the Controller
+
+If you want to extend the controller, see [Extending the Controller](../extending/basecontroller.md).
+
+## That's it!
+
+That, in a nutshell, is all there is to know about controllers.
diff --git a/docs/incoming/filters.md b/docs/incoming/filters.md
new file mode 100644
index 000000000000..db6033208191
--- /dev/null
+++ b/docs/incoming/filters.md
@@ -0,0 +1,497 @@
+# Controller Filters
+
+Controller Filters allow you to perform actions either before or after the controllers execute. Unlike [events](../extending/events.md), you can choose the specific URIs or routes to which the filters will be applied. Before filters may modify the Request while after filters can act on and even modify the Response, allowing for a lot of flexibility and power.
+
+Some common examples of tasks that might be performed with filters are:
+
+- Performing CSRF protection on the incoming requests
+- Restricting areas of your site based upon their Role
+- Performing rate limiting on certain endpoints
+- Displaying a "Down for Maintenance" page
+- Performing automatic content negotiation
+- and more...
+
+## Creating a Filter
+
+Filters are simple classes that implement `CodeIgniter\Filters\FilterInterface`. They contain two methods: `before()` and `after()`, which hold the code that will run before and after the controller respectively. Your class must contain both methods but may leave the methods empty if they are not needed. A skeleton filter class looks like:
+
+```php
+isLoggedIn()) {
+ return redirect()->to(site_url('login'));
+ }
+ }
+}
+```
+
+If a `Response` instance is returned, the Response will be sent back to the client and script execution will stop. This can be useful for implementing rate limiting for APIs. See [Throttler](../libraries/throttler.md) for an example.
+
+### After Filters
+
+After filters are nearly identical to before filters, except that you can only return the `$response` object, and you cannot stop script execution. This does allow you to modify the final output, or simply do something with the final output. This could be used to ensure certain security headers were set the correct way, or to cache the final output, or even to filter the final output with a bad words filter.
+
+## Configuring Filters
+
+There are two ways to configure filters when they get run. One is done in **app/Config/Filters.php**, and the other is done in **app/Config/Routes.php**.
+
+If you want to specify filters for defined routes, use **app/Config/Routes.php** and see [URI Routing](routing.md#applying-filters).
+
+!!! note "Note"
+ The safest way to apply filters is to [disable auto-routing](routing.md#use-defined-routes-only), and [set filters to routes](routing.md#applying-filters).
+
+### app/Config/Filters.php
+
+The **app/Config/Filters.php** file contains four properties that allow you to configure exactly when the filters run.
+
+!!! warning "Warning"
+ It is recommended that you always add `*` at the end of a URI in the filter settings, because a controller method might be accessible by different URLs than you think. For example, when [Auto Routing (Legacy)](routing.md#auto-routing-legacy) is enabled, if you have `Blog::index()`, it can be accessed with `blog`, `blog/index`, `blog/index/1`, etc.
+
+#### $aliases
+
+The `$aliases` array is used to associate a simple name with one or more fully-qualified class names that are the filters to run:
+
+```php
+ \CodeIgniter\Filters\CSRF::class,
+ ];
+
+ // ...
+}
+```
+
+Aliases are mandatory, and if you try to use a full class name later, the system will throw an error.
+
+Defining them in this way makes it simple to switch out the class used. This is great for when you decide you need to change to a different authentication system, since you only change the filter's class and you're done.
+
+You can combine multiple filters into one alias, making complex sets of filters simple to apply:
+
+```php
+ [
+ \App\Filters\Negotiate::class,
+ \App\Filters\ApiAuth::class,
+ ],
+ ];
+
+ // ...
+}
+```
+
+You should define as many aliases as you need.
+
+#### $required
+
+!!! success "Available from version 4.5.0"
+
+The second section allows you to define **Required Filters**. They are special filters that are applied to every request made by the framework. They are applied before and after other kinds of filters that are explained below.
+
+!!! note "Note"
+ The Required Filters are always executed. However, if the route does not exist, only the Before Filters are executed.
+
+You should take care with how many you use here, since it could have performance implications to have too many run on every request. But the filters set by default provide framework functionality. If removed, those functions will no longer work. See [Provided Filters](#provided-filters) for details.
+
+Filters can be specified by adding their alias to either the `before` or `after` array:
+
+```php
+ [
+ 'forcehttps', // Force Global Secure Requests
+ 'pagecache', // Web Page Caching
+ ],
+ 'after' => [
+ 'pagecache', // Web Page Caching
+ 'performance', // Performance Metrics
+ 'toolbar', // Debug Toolbar
+ ],
+ ];
+
+ // ...
+}
+```
+
+#### $globals
+
+The third section allows you to define any filters that should be applied to every valid request made by the framework.
+
+You should take care with how many you use here, since it could have performance implications to have too many run on every request.
+
+Filters can be specified by adding their alias to either the `before` or `after` array:
+
+```php
+ [
+ 'csrf',
+ ],
+ 'after' => [],
+ ];
+
+ // ...
+}
+```
+
+##### Except for a Few URIs
+
+There are times when you want to apply a filter to almost every request, but have a few that should be left alone. One common example is if you need to exclude a few URIs from the CSRF protection filter to allow requests from third-party websites to hit one or two specific URIs, while keeping the rest of them protected.
+
+To do this, add an array with the `except` key and a URI path (relative to BaseURL) to match as the value alongside the alias:
+
+```php
+ [
+ 'csrf' => ['except' => 'api/*'],
+ ],
+ 'after' => [],
+ ];
+
+ // ...
+}
+```
+
+!!! warning "Warning"
+ Prior to v4.4.7, due to a bug, the URI paths processed by the filter were not URL-decoded. In other words, the URI paths specified in the routing and the URI paths specified in the filter could be different. See [Paths in Controller Filters](../installation/upgrade_447.md#paths-in-controller-filters) for details.
+
+Any place you can use a URI path (relative to BaseURL) in the filter settings, you can use a regular expression or, as in the example above, use an asterisk (`*`) for a wildcard that will match all characters after that. In this example, any URI path starting with `api/` would be exempted from CSRF protection, but the site's forms would all be protected.
+
+If you need to specify multiple URI paths, you can use an array of URI path patterns:
+
+```php
+ [
+ 'csrf' => ['except' => ['foo/*', 'bar/*']],
+ ],
+ 'after' => [],
+ ];
+
+ // ...
+}
+```
+
+#### $methods
+
+!!! warning "Warning"
+ If you use `$methods` filters, you should [disable Auto Routing (Legacy)](routing.md#use-defined-routes-only) because [Auto Routing (Legacy)](routing.md#auto-routing-legacy) permits any HTTP method to access a controller. Accessing the controller with a method you don't expect could bypass the filter.
+
+You can apply filters to all requests of a certain HTTP method, like `POST`, `GET`, `PUT`, etc. Its value would be an array of filters to run:
+
+```php
+ ['invalidchars', 'csrf'],
+ 'GET' => ['csrf'],
+ ];
+
+ // ...
+}
+```
+
+!!! note "Note"
+ Unlike the `$globals` or the `$filters` properties, these will only run as before filters.
+
+In addition to the standard HTTP methods, this also supports one special case: `CLI`. The `CLI` method would apply to all requests that were run from the command line.
+
+!!! note "Note"
+ Prior to v4.5.0, due to a bug, you needed to specify the HTTP method names in **lowercase**.
+
+#### $filters
+
+This property is an array of filter aliases. For each alias, you can specify `before` and `after` arrays that contain a list of URI path (relative to BaseURL) patterns that the filter should apply to:
+
+```php
+ ['before' => ['admin/*'], 'after' => ['users/*']],
+ 'bar' => ['before' => ['api/*', 'admin/*']],
+ ];
+
+ // ...
+}
+```
+
+!!! warning "Warning"
+ Prior to v4.4.7, due to a bug, the URI paths processed by the filter were not URL-decoded. In other words, the URI paths specified in the routing and the URI paths specified in the filter could be different. See [Paths in Controller Filters](../installation/upgrade_447.md#paths-in-controller-filters) for details.
+
+##### Filter Arguments
+
+!!! success "Available from version 4.4.0"
+
+When configuring `$filters`, additional arguments may be passed to a filter:
+
+```php
+ ['before' => ['admin/*']],
+ 'permission:users.manage' => ['before' => ['admin/users/*']],
+ ];
+
+ // ...
+}
+```
+
+In this example, when the URI matches `admin/*`, the array `['admin', 'superadmin']` will be passed in `$arguments` to the `group` filter's `before()` method. When the URI matches `admin/users/*`, the array `['users.manage']` will be passed in `$arguments` to the `permission` filter's `before()` method.
+
+!!! note "Note"
+ Prior to v4.6.0, the same filter could not be run multiple times with different arguments.
+
+### Filter Execution Order
+
+!!! important "Important"
+ Starting with v4.5.0, the order in which filters are executed has changed. If you wish to maintain the same execution order as in previous versions, you must set `Config\Feature::$oldFilterOrder` to `true`.
+
+Filters are executed in the following order:
+
+- **Before Filters**: required → globals → methods → filters → route
+- **After Filters**: route → filters → globals → required
+
+!!! note "Note"
+ The *required* filters can be used since v4.5.0.
+
+!!! note "Note"
+ Prior to v4.5.0, the filters specified for a route (in **app/Config/Routes.php**) were executed before the filters specified in **app/Config/Filters.php**. And the execution order of the After Filters in Route filters and Filters filters was not reversed. See [Upgrading Guide](../installation/upgrade_450.md#filter-execution-order) for details.
+
+## Confirming Filters
+
+CodeIgniter has the following [command](../cli/spark_commands.md) to check the filters for a route.
+
+### filter:check
+
+!!! success "Available from version 4.3.0"
+
+For example, check the filters for the route `/` with the **GET** method:
+
+```console
+php spark filter:check get /
+```
+
+The output is like the following:
+
+```none
++--------+-------+----------------------+-------------------------------+
+| Method | Route | Before Filters | After Filters |
++--------+-------+----------------------+-------------------------------+
+| GET | / | forcehttps pagecache | pagecache performance toolbar |
++--------+-------+----------------------+-------------------------------+
+
+Before Filter Classes:
+CodeIgniter\Filters\ForceHTTPS → CodeIgniter\Filters\PageCache
+After Filter Classes:
+CodeIgniter\Filters\PageCache → CodeIgniter\Filters\PerformanceMetrics → CodeIgniter\Filters\DebugToolbar
+```
+
+!!! note "Note"
+ Since v4.6.0, filter arguments are displayed in the output table. Also, the actual filter classnames are displayed at the end.
+
+You can also see the routes and filters with the `spark routes` command, but it might not show accurate filters when you use regular expressions for routes. See [URI Routing](routing.md#spark-routes) for details.
+
+## Provided Filters
+
+The filters bundled with CodeIgniter4 are:
+
+- `cors` => [Cross-Origin Resource Sharing (CORS)](../libraries/cors.md)
+- `csrf` => [CSRF](../libraries/security.md#cross-site-request-forgery-csrf)
+- `toolbar` => [DebugToolbar](../testing/debugging.md#the-debug-toolbar)
+- `honeypot` => [Honeypot](../libraries/honeypot.md)
+- `invalidchars` => [InvalidChars](#invalidchars)
+- `secureheaders` => [SecureHeaders](#secureheaders)
+- `forcehttps` => [ForceHTTPS](#forcehttps)
+- `pagecache` => [PageCache](../general/caching.md)
+- `performance` => [PerformanceMetrics](#performancemetrics)
+
+!!! note "Note"
+ The filters are executed in the order defined in the config file. However, if enabled, `DebugToolbar` is always executed last because it should be able to capture everything that happens in the other filters.
+
+### ForceHTTPS
+
+!!! success "Available from version 4.5.0"
+
+This filter provides the "Force Global Secure Requests" feature.
+
+If you set `Config\App::$forceGlobalSecureRequests` to 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 (HSTS) header will be set.
+
+### PerformanceMetrics
+
+!!! success "Available from version 4.5.0"
+
+This filter provides the pseudo-variables for performance metrics.
+
+If you would like to display the total elapsed time from the moment CodeIgniter starts to the moment right before the final output is sent to the browser, simply place this pseudo-variable in one of your views:
+
+```
+{elapsed_time}
+```
+
+If you would like to show your memory usage in your view files, use this pseudo-variable:
+
+```
+{memory_usage}
+```
+
+If you don't need this feature, remove `'performance'` from `$required['after']`.
+
+### InvalidChars
+
+This filter prohibits user input data (`$_GET`, `$_POST`, `$_COOKIE`, `php://input`) from containing the following characters:
+
+- invalid UTF-8 characters
+- control characters except line break and tab code
+
+### SecureHeaders
+
+This filter adds HTTP response headers that your application can use to increase the security of your application.
+
+If you want to customize the headers, extend `CodeIgniter\Filters\SecureHeaders` and override the `$headers` property. And change the `$aliases` property in **app/Config/Filters.php**:
+
+```php
+ \App\Filters\SecureHeaders::class,
+ ];
+
+ // ...
+}
+```
+
+If you want to know about secure headers, see [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/).
diff --git a/docs/incoming/incomingrequest.md b/docs/incoming/incomingrequest.md
new file mode 100644
index 000000000000..ccae7d1db668
--- /dev/null
+++ b/docs/incoming/incomingrequest.md
@@ -0,0 +1,750 @@
+# IncomingRequest Class
+
+The IncomingRequest class provides an object-oriented representation of an HTTP request from a client, like a browser. It extends from, and has access to all the methods of the [Request](request.md) and [Message](message.md) classes, in addition to the methods listed below.
+
+## Accessing the Request
+
+An instance of the request class is already populated for you if the current class is a descendant of `CodeIgniter\Controller` and can be accessed as a class property:
+
+```php
+request->isAJAX()) {
+ // ...
+ }
+ }
+}
+```
+
+If you are not within a controller, but still need access to the application's Request object, you can get a copy of it through the [Services class](../concepts/services.md):
+
+```php
+request = $request;
+ }
+}
+
+$someClass = new SomeClass(service('request'));
+```
+
+## Determining Request Type
+
+A request could be of several types, including an AJAX request or a request from the command line. This can be checked with the `isAJAX()` and `isCLI()` methods:
+
+```php
+isAJAX()) {
+ // ...
+}
+
+// Check for CLI Request
+if ($request->isCLI()) {
+ // ...
+}
+```
+
+!!! note "Note"
+ The `isAJAX()` method depends on the `X-Requested-With` header, which in some cases is not sent by default in XHR requests via JavaScript (i.e., fetch). See the [AJAX Requests](../general/ajax.md) section on how to avoid this problem.
+
+### is()
+
+!!! success "Available from version 4.3.0"
+
+Since v4.3.0, you can use the `is()` method. It accepts an HTTP method, `'ajax'`, or `'json'`, and returns a boolean.
+
+!!! note "Note"
+ The HTTP method should be case-sensitive, but the parameter is case-insensitive.
+
+```php
+is('get');
+$request->is('post');
+$request->is('put');
+$request->is('delete');
+$request->is('head');
+$request->is('patch');
+$request->is('options');
+
+// Checks if it is an AJAX request. The same as `$request->isAJAX()`.
+$request->is('ajax');
+
+// Checks if it is a JSON request.
+$request->is('json');
+```
+
+### getMethod()
+
+You can check the HTTP method that this request represents with the `getMethod()` method:
+
+```php
+getMethod();
+```
+
+The HTTP method is case-sensitive, and by convention, standardized methods are defined in all-uppercase US-ASCII letters.
+
+!!! note "Note"
+ Prior to v4.5.0, by default, the method was returned as a lowercase string (i.e., `'get'`, `'post'`, etc.). This was a bug.
+
+You can get a lowercase version by wrapping the call in `strtolower()`:
+
+```
+// Returns 'get'
+$method = strtolower($request->getMethod());
+```
+
+You can also check if the request was made through an HTTPS connection with the `isSecure()` method:
+
+```php
+isSecure()) {
+ force_https();
+}
+```
+
+!!! important "Important"
+ The `X-Forwarded-Proto` and `Front-End-Https` headers are taken into account only when the request comes from a trusted proxy configured in `Config\App::$proxyIPs`. Otherwise, they are client-supplied and could be spoofed.
+
+!!! note "Note"
+ On dual-stack servers, a proxy's IPv4 address may be reported as an IPv4-mapped IPv6 address (e.g., `::ffff:192.168.5.21`), which does not match an IPv4 entry such as `192.168.5.0/24`. In that case, add the IPv4-mapped form to `Config\App::$proxyIPs`, e.g., `::ffff:192.168.5.21` or `::ffff:192.168.5.0/120`.
+
+## Retrieving Input
+
+You can retrieve input from `$_GET`, `$_POST`, `$_COOKIE`, `$_SERVER` and `$_ENV` through the Request object. The data is not automatically filtered and returns the raw input data as passed in the request.
+
+!!! note "Note"
+ It is bad practice to use global variables. Basically, they should be avoided, and it is recommended to use methods of the Request object.
+
+The main advantages to using these methods instead of accessing them directly (`$_POST['something']`) are that they will return null if the item doesn't exist, and you can have the data filtered. This lets you conveniently use data without having to test whether an item exists first. In other words, normally you might do something like this:
+
+```php
+getPost('foo');
+```
+
+### Getting Data
+
+#### getGet()
+
+The `getGet()` method will pull from `$_GET`.
+
+- `$request->getGet()`
+
+#### getPost()
+
+The `getPost()` method will pull from `$_POST`.
+
+- `$request->getPost()`
+
+#### getCookie()
+
+The `getCookie()` method will pull from `$_COOKIE`.
+
+- `$request->getCookie()`
+
+#### getServer()
+
+The `getServer()` method will pull from `$_SERVER`.
+
+- `$request->getServer()`
+
+#### getPostGet()
+
+In addition, there are a few utility methods for retrieving information from either `$_GET` or `$_POST`, while maintaining the ability to control the order you look for it:
+
+- `$request->getPostGet()` - checks `$_POST` first, then `$_GET`
+
+#### getGetPost()
+
+- `$request->getGetPost()` - checks `$_GET` first, then `$_POST`
+
+#### getVar()
+
+!!! important "Important"
+ This method exists only for backward compatibility. Do not use it in new projects. Even if you are already using it, we recommend that you use another, more appropriate method.
+
+The `getVar()` method will pull from `$_REQUEST`, so it will return any data from `$_GET`, `$_POST`, or `$_COOKIE` (depending on **php.ini** [request-order](https://www.php.net/manual/en/ini.core.php#ini.request-order)).
+
+!!! warning "Warning"
+ If you want to validate POST data only, don't use `getVar()`. Newer values override older values. POST values may be overridden by the cookies if they have the same name, and you set "C" after "P" in [request-order](https://www.php.net/manual/en/ini.core.php#ini.request-order).
+
+!!! note "Note"
+ If the incoming request has a `Content-Type` header set to `application/json`, the `getVar()` method returns the JSON data instead of `$_REQUEST` data.
+
+### Getting JSON Data
+
+You can grab the contents of `php://input` as a JSON stream with `getJSON()`.
+
+!!! note "Note"
+ This has no way of checking if the incoming data is valid JSON or not, so you should only use this method if you know that you're expecting JSON.
+
+```php
+getJSON();
+```
+
+By default, this will return any objects in the JSON data as objects. If you want that converted to associative arrays, pass in `true` as the first parameter.
+
+The second and third parameters match up to the `$depth` and `$flags` arguments of the [json_decode()](https://www.php.net/manual/en/function.json-decode.php) PHP function.
+
+### Getting Specific Data from JSON
+
+You can get a specific piece of data from a JSON stream by passing a variable name into `getJsonVar()` for the data that you want, or you can use "dot" notation to dig into the JSON to get data that is not on the root level.
+
+```php
+getJsonVar('foo');
+// $data = "bar"
+
+$data = $request->getJsonVar('fizz.buzz');
+// $data = "baz"
+```
+
+If you want the result to be an associative array instead of an object, you can pass true in the second parameter:
+
+```php
+getJsonVar('fizz');
+// $data->buzz = "baz"
+
+$data = $request->getJsonVar('fizz', true);
+// $data = ["buzz" => "baz"]
+```
+
+!!! note "Note"
+ See the documentation for `dot_array_search()` in the `Array` helper for more information on "dot" notation.
+
+### Retrieving Raw Data (PUT, PATCH, DELETE)
+
+Finally, you can grab the contents of `php://input` as a raw stream with `getRawInput()`:
+
+```php
+getRawInput();
+```
+
+This will retrieve data and convert it to an array. Like this:
+
+```php
+getRawInput());
+
+/*
+ * Outputs:
+ * [
+ * 'Param1' => 'Value1',
+ * 'Param2' => 'Value2',
+ * ]
+ */
+```
+
+You can also use `getRawInputVar()` to get the specified variable from the raw stream and filter it.
+
+```php
+getRawInputVar('bar'));
+// Outputs: two
+
+// foo=one&bar=two&baz[]=10&baz[]=20
+var_dump($request->getRawInputVar(['foo', 'bar']));
+/*
+ * Outputs:
+ * [
+ * 'foo' => 'one',
+ * 'bar' => 'two'
+ * ]
+ */
+
+// foo=one&bar=two&baz[]=10&baz[]=20
+var_dump($request->getRawInputVar('baz'));
+/*
+ * Outputs:
+ * [
+ * '10',
+ * '20'
+ * ]
+ */
+
+// foo=one&bar=two&baz[]=10&baz[]=20
+var_dump($request->getRawInputVar('baz.0'));
+// Outputs: 10
+```
+
+### Filtering Input Data
+
+To maintain the security of your application, you will want to filter all input as you access it. You can pass the type of filter to use as the second parameter of any of these methods. The native `filter_var()` function is used for the filtering. Head over to the PHP manual for a list of [valid filter types](https://www.php.net/manual/en/filters.php).
+
+Filtering a POST variable would look like this:
+
+```php
+getPost('email', FILTER_SANITIZE_EMAIL);
+```
+
+All of the methods mentioned above support the filter type passed in as the second parameter, with the exception of `getJSON()` and `getRawInput()`.
+
+## Retrieving Headers
+
+You can get access to any header that was sent with the request with the `headers()` method, which returns an array of all headers, with the key as the name of the header, and the value as an instance of `CodeIgniter\HTTP\Header`:
+
+```php
+headers());
+
+/*
+ * Outputs:
+ * [
+ * 'Host' => CodeIgniter\HTTP\Header,
+ * 'Cache-Control' => CodeIgniter\HTTP\Header,
+ * 'Accept' => CodeIgniter\HTTP\Header,
+ * ]
+ */
+```
+
+If you only need a single header, you can pass the name into the `header()` method. This will grab the specified header object in a case-insensitive manner if it exists. If not, then it will return `null`:
+
+```php
+header('host');
+$host = $request->header('Host');
+$host = $request->header('HOST');
+```
+
+You can always use `hasHeader()` to see if the header existed in this request:
+
+```php
+hasHeader('DNT')) {
+ // Don't track something...
+}
+```
+
+If you need the value of a header as a string with all values on one line, you can use the `getHeaderLine()` method:
+
+```php
+getHeaderLine('accept-encoding');
+```
+
+If you need the entire header, with the name and values in a single string, simply cast the header as a string:
+
+```php
+getUri()` method. You can cast this object as a string to get a full URL for the current request:
+
+```php
+getUri();
+```
+
+The object gives you the full ability to grab any part of the request on its own:
+
+```php
+getUri();
+
+echo $uri->getScheme(); // http
+echo $uri->getAuthority(); // snoopy:password@example.com:88
+echo $uri->getUserInfo(); // snoopy:password
+echo $uri->getHost(); // example.com
+echo $uri->getPort(); // 88
+echo $uri->getPath(); // /path/to/page
+echo $uri->getRoutePath(); // path/to/page
+echo $uri->getQuery(); // foo=bar&bar=baz
+print_r($uri->getSegments()); // Array ( [0] => path [1] => to [2] => page )
+echo $uri->getSegment(1); // path
+echo $uri->getTotalSegments(); // 3
+```
+
+You can work with the current URI string (the path relative to your baseURL) using the `getRoutePath()` method.
+
+!!! note "Note"
+ The `getRoutePath()` method can be used since v4.4.0. Prior to v4.4.0, the `getPath()` method returned the path relative to your baseURL.
+
+## Uploaded Files
+
+Information about all uploaded files can be retrieved through `$request->getFiles()`, which returns an array of `CodeIgniter\HTTP\Files\UploadedFile` instances. This helps to ease the pain of working with uploaded files, and uses best practices to minimize any security risks.
+
+```php
+getFiles();
+```
+
+See [Working with Uploaded Files](../libraries/uploaded_files.md#accessing-files) for the details.
+
+You can retrieve a single file uploaded on its own, based on the filename given in the HTML file input:
+
+```php
+getFile('userfile');
+```
+
+You can retrieve an array of same-named files uploaded as part of a multi-file upload, based on the filename given in the HTML file input:
+
+```php
+getFileMultiple('userfile');
+```
+
+!!! note "Note"
+ The files here correspond to `$_FILES`. Even if a user just clicks the submit button of a form and does not upload any file, the file will still exist. You can check that the file was actually uploaded with the `isValid()` method in UploadedFile. See [Verify a File](../libraries/uploaded_files.md#verify-a-file) for more details.
+
+## Content Negotiation
+
+You can easily negotiate content types with the request through the `negotiate()` method:
+
+```php
+negotiate('language', ['en-US', 'en-GB', 'fr', 'es-mx']);
+$imageType = $request->negotiate('media', ['image/png', 'image/jpg']);
+$charset = $request->negotiate('charset', ['UTF-8', 'UTF-16']);
+$contentType = $request->negotiate('media', ['text/html', 'text/xml']);
+$encoding = $request->negotiate('encoding', ['gzip', 'compress']);
+```
+
+See the [Content Negotiation](content_negotiation.md) page for more details.
+
+## Class Reference
+
+!!! note "Note"
+ In addition to the methods listed here, this class inherits the methods from the [Request Class](request.md) and the [Message Class](message.md).
+
+The methods provided by the parent classes that are available are:
+
+- `CodeIgniter\\HTTP\\Request::getIPAddress`
+- `CodeIgniter\\HTTP\\Request::isValidIP`
+- `CodeIgniter\\HTTP\\Request::getMethod`
+- `CodeIgniter\\HTTP\\Request::setMethod`
+- `CodeIgniter\\HTTP\\Request::getServer`
+- `CodeIgniter\\HTTP\\Request::setGlobal`
+- `CodeIgniter\\HTTP\\Request::fetchGlobal`
+- `CodeIgniter\\HTTP\\Message::getBody`
+- `CodeIgniter\\HTTP\\Message::setBody`
+- `CodeIgniter\\HTTP\\Message::appendBody`
+- `CodeIgniter\\HTTP\\Message::populateHeaders`
+- `CodeIgniter\\HTTP\\Message::headers`
+- `CodeIgniter\\HTTP\\Message::header`
+- `CodeIgniter\\HTTP\\Message::hasHeader`
+- `CodeIgniter\\HTTP\\Message::getHeaderLine`
+- `CodeIgniter\\HTTP\\Message::setHeader`
+- `CodeIgniter\\HTTP\\Message::removeHeader`
+- `CodeIgniter\\HTTP\\Message::appendHeader`
+- `CodeIgniter\\HTTP\\Message::prependHeader`
+- `CodeIgniter\\HTTP\\Message::getProtocolVersion`
+- `CodeIgniter\\HTTP\\Message::setProtocolVersion`
+
+## CodeIgniter\HTTP
+
+### IncomingRequest
+
+#### isCLI()
+
+- **Returns**: True if the request was initiated from the command line, otherwise false.
+- **Return type**: `bool`
+
+#### isAJAX()
+
+- **Returns**: True if the request is an AJAX request, otherwise false.
+- **Return type**: `bool`
+
+#### isSecure()
+
+- **Returns**: True if the request is an HTTPS request, otherwise false.
+- **Return type**: `bool`
+
+!!! important "Important"
+ The `X-Forwarded-Proto` and `Front-End-Https` headers are taken into account only when the request comes from a trusted proxy configured in `Config\App::$proxyIPs`.
+
+#### getVar([$index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$index** `string` The name of the variable/key to look for.
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in [Types of filters](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int` Flags to apply. A list of flags can be found in [Filter flags](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_REQUEST` if no parameters supplied, otherwise the REQUEST value if found, or null if not
+
+- **Return type**: `array|bool|float|int|object|string|null`
+
+!!! important "Important"
+ This method exists only for backward compatibility. Do not use it in new projects. Even if you are already using it, we recommend that you use another, more appropriate method.
+
+This method is identical to `getGet()`, only it fetches REQUEST data.
+
+#### getGet([$index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$index** `array|string` The name of the variable/key to look for, or an array of names.
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in [Types of filters](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int` Flags to apply. A list of flags can be found in [Filter flags](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_GET` if no parameters supplied, otherwise the GET value if found, or null if not
+
+- **Return type**: `array|bool|float|int|object|string|null`
+
+The first parameter will contain the name of the GET item you are looking for:
+
+```php
+getGet('some_data');
+```
+
+The method returns null if the item you are attempting to retrieve does not exist.
+
+The second optional parameter lets you run the data through PHP's filters. Pass in the desired filter type as the second parameter:
+
+```php
+getGet('some_data', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+```
+
+To return an array of all GET items, call without any parameters.
+
+To return all GET items and pass them through the filter, set the first parameter to null while setting the second parameter to the filter you want to use:
+
+```php
+getGet(null, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+// returns all GET items with string sanitation
+```
+
+To return an array of multiple GET parameters, pass all the required keys as an array:
+
+```php
+getGet(['field1', 'field2']);
+```
+
+The same rule applies here: to retrieve the parameters with filtering, set the second parameter to the filter type to apply:
+
+```php
+getGet(['field1', 'field2'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
+```
+
+#### getPost([$index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$index** `array|string` The name of the variable/key to look for, or an array of names.
+ - **$filter** `int` The type of filter to apply. A list of filters can be found [here](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int` Flags to apply. A list of flags can be found [here](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_POST` if no parameters supplied, otherwise the POST value if found, or null if not
+
+- **Return type**: `array|bool|float|int|object|string|null`
+
+This method is identical to `getGet()`, only it fetches POST data.
+
+#### getPostGet([$index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$index** `array|string` The name of the variable/key to look for, or an array of names.
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in [Types of filters](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int` Flags to apply. A list of flags can be found in [Filter flags](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_POST` and `$_GET` combined if no parameters specified (prefer POST value on conflict),
+ otherwise looks for POST value, if nothing found looks for GET value, if no value found returns null
+
+- **Return type**: `array|bool|float|int|object|string|null`
+
+This method works pretty much the same way as `getPost()` and `getGet()`, only combined. It will search through both POST and GET streams for data, looking first in POST, and then in GET:
+
+```php
+getPostGet('field1');
+```
+
+If an array of indexes is specified, the method returns an associative array and applies the POST-then-GET lookup order to each index.
+
+If no index is specified, it will return both POST and GET streams combined, although POST data will be preferred in case of a name conflict.
+
+#### getGetPost([$index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$index** `array|string` The name of the variable/key to look for, or an array of names.
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in [Types of filters](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int` Flags to apply. A list of flags can be found in [Filter flags](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_GET` and `$_POST` combined if no parameters specified (prefer GET value on conflict),
+ otherwise looks for GET value, if nothing found looks for POST value, if no value found returns null
+
+- **Return type**: `array|bool|float|int|object|string|null`
+
+This method works pretty much the same way as `getPost()` and `getGet()`, only combined. It will search through both GET and POST streams for data, looking first in GET, and then in POST:
+
+```php
+getGetPost('field1');
+```
+
+If an array of indexes is specified, the method returns an associative array and applies the GET-then-POST lookup order to each index.
+
+If no index is specified, it will return both GET and POST streams combined, although GET data will be preferred in case of a name conflict.
+
+#### getCookie([$index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$index** `array|string|null` COOKIE name
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in [Types of filters](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int` Flags to apply. A list of flags can be found in [Filter flags](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_COOKIE` if no parameters supplied, otherwise the COOKIE value if found or null if not
+
+- **Return type**: `array|bool|float|int|object|string|null`
+
+This method is identical to `getPost()` and `getGet()`, only it fetches cookie data:
+
+```php
+getCookie('some_cookie');
+$request->getCookie('some_cookie', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // with filter
+```
+
+To return an array of multiple cookie values, pass all the required keys as an array:
+
+```php
+getCookie(['some_cookie', 'some_cookie2']);
+```
+
+!!! note "Note"
+ Unlike the [Cookie Helper](../helpers/cookie_helper.md) function `get_cookie()`, this method does NOT prepend your configured `Config\Cookie::$prefix` value.
+
+#### getServer([$index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$index** `array|string|null` Value name
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in [Types of filters](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int` Flags to apply. A list of flags can be found in [Filter flags](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_SERVER` item value if found, null if not
+
+- **Return type**: `array|bool|float|int|object|string|null`
+
+This method is identical to the `getPost()`, `getGet()` and `getCookie()` methods, only it fetches Server data (`$_SERVER`):
+
+```php
+getServer('some_data');
+```
+
+To return an array of multiple `$_SERVER` values, pass all the required keys as an array.
+
+```php
+getServer(['SERVER_PROTOCOL', 'REQUEST_URI']);
+```
+
+#### getUserAgent()
+
+- **Returns**: The UserAgent object for the request.
+- **Return type**: `CodeIgniter\HTTP\UserAgent`
+
+This method returns the User Agent instance from the SERVER data:
+
+```php
+getUserAgent();
+```
+
+#### getPath()
+
+- **Returns**: The current URI path relative to baseURL
+- **Return type**: `string`
+
+This method returns the current URI path relative to baseURL.
+
+!!! note "Note"
+ Prior to v4.4.0, this was the safest method to determine the "current URI", since `IncomingRequest::$uri` might not be aware of the complete App configuration for base URLs.
diff --git a/docs/incoming/index.md b/docs/incoming/index.md
new file mode 100644
index 000000000000..2975bffe0a2d
--- /dev/null
+++ b/docs/incoming/index.md
@@ -0,0 +1,15 @@
+# Controllers and Routing
+
+Controllers handle incoming requests.
+
+- [URI Routing](routing.md)
+- [Controllers](controllers.md)
+- [Controller Filters](filters.md)
+- [Controller Attributes](controller_attributes.md)
+- [Auto Routing (Improved)](auto_routing_improved.md)
+- [HTTP Messages](message.md)
+- [Request Class](request.md)
+- [IncomingRequest Class](incomingrequest.md)
+- [Content Negotiation](content_negotiation.md)
+- [HTTP Method Spoofing](methodspoofing.md)
+- [RESTful Resource Handling](restful.md)
diff --git a/docs/incoming/message.md b/docs/incoming/message.md
new file mode 100644
index 000000000000..a697c9965663
--- /dev/null
+++ b/docs/incoming/message.md
@@ -0,0 +1,262 @@
+# HTTP Messages
+
+The Message class provides an interface to the portions of an HTTP message that are common to both requests and responses, including the message body, protocol version, utilities for working with the headers, and methods for handling content negotiation.
+
+This class is the parent class that both the [Request Class](../incoming/request.md) and the [Response Class](../outgoing/response.md) extend from, and it is not used directly.
+
+## Class Reference
+
+## CodeIgniter\HTTP
+
+### Message
+
+#### getBody()
+
+- **Returns**: The current message body
+- **Return type**: `mixed`
+
+Returns the current message body, if any has been set. If no body exists, returns null:
+
+```php
+getBody();
+```
+
+#### setBody($data)
+
+- **Parameters**
+ - **$data** `mixed` The body of the message.
+
+- **Returns**: The Message|Response instance to allow methods to be chained together.
+
+- **Return type**: `CodeIgniter\HTTP\Message|CodeIgniter\HTTP\Response`
+
+Sets the body of the current request.
+
+#### appendBody($data)
+
+- **Parameters**
+ - **$data** `mixed` The body of the message.
+
+- **Returns**: The Message|Response instance to allow methods to be chained together.
+
+- **Return type**: `CodeIgniter\HTTP\Message|CodeIgniter\HTTP\Response`
+
+Appends data to the body of the current request.
+
+#### populateHeaders()
+
+- **Returns**: void
+
+Scans and parses the headers found in the SERVER data and stores them for later access. This is used by the [IncomingRequest Class](../incoming/incomingrequest.md) to make the current request's headers available.
+
+The headers are any SERVER data that starts with `HTTP_`, like `HTTP_HOST`. Each message is converted from its standard uppercase and underscore format to a ucwords and dash format. The preceding `HTTP_` is removed from the string. So `HTTP_ACCEPT_LANGUAGE` becomes `Accept-Language`.
+
+#### headers()
+
+- **Returns**: An array of all of the headers found.
+- **Return type**: `array`
+
+Returns an array of all headers found or previously set.
+
+#### header($name)
+
+- **Parameters**
+ - **$name** `string` The name of the header you want to retrieve the value of.
+
+- **Returns**: Returns a single header object. If multiple headers with the same name exist, it will return an array of header objects.
+
+- **Return type**: `\CodeIgniter\HTTP\Header|array`
+
+Allows you to retrieve the current value of a single message header. `$name` is the case-insensitive header name. While the header is converted internally as described above, you can access the header with any type of case:
+
+```php
+header('HOST');
+$message->header('Host');
+$message->header('host');
+```
+
+If the header has multiple values, `getValue()` will return an array of values. You can use the `getValueLine()` method to retrieve the values as a string:
+
+```php
+header('Accept-Language');
+/*
+ * Outputs something like:
+ * 'Accept-Language: en,en-US'
+ */
+
+echo $message->header('Accept-Language')->getValue();
+/*
+ * Outputs something like:
+ * [
+ * 'en',
+ * 'en-US',
+ * ]
+ */
+
+echo $message->header('Accept-Language')->getValueLine();
+/*
+ * Outputs something like:
+ * en,en-US'
+ */
+```
+
+You can filter the header by passing a filter value in as the second parameter:
+
+```php
+header('Document-URI', FILTER_SANITIZE_URL);
+```
+
+#### hasHeader($name)
+
+- **Parameters**
+ - **$name** `string` The name of the header to check.
+
+- **Returns**: Returns true if it exists, false otherwise.
+
+- **Return type**: `bool`
+
+#### getHeaderLine($name)
+
+- **Parameters**
+ - **$name** `string` The name of the header to retrieve.
+
+- **Returns**: A string representing the header value.
+
+- **Return type**: `string`
+
+Returns the value(s) of the header as a string. This method allows you to easily get a string representation of the header values when the header has multiple values. The values are appropriately joined:
+
+```php
+getHeaderLine('Accept-Language');
+/*
+ * Outputs:
+ * 'en,en-US'
+ */
+```
+
+#### setHeader($name, $value)
+
+- **Parameters**
+ - **$name** `string` The name of the header to set the value for.
+ - **$value** `mixed` The value to set the header to.
+
+- **Returns**: The current Message|Response instance
+
+- **Return type**: `CodeIgniter\HTTP\Message|CodeIgniter\HTTP\Response`
+
+Sets the value of a single header. `$name` is the case-insensitive name of the header. If the header doesn't already exist in the collection, it will be created. The `$value` can be either a string or an array of strings:
+
+```php
+setHeader('Host', 'codeigniter.com');
+```
+
+#### removeHeader($name)
+
+- **Parameters**
+ - **$name** `string` The name of the header to remove.
+
+- **Returns**: The current message instance
+
+- **Return type**: `CodeIgniter\HTTP\Message`
+
+Removes the header from the Message. `$name` is the case-insensitive name of the header:
+
+```php
+removeHeader('Host');
+```
+
+#### appendHeader($name, $value)
+
+- **Parameters**
+ - **$name** `string` The name of the header to modify
+ - **$value** `string` The value to add to the header.
+
+- **Returns**: The current message instance
+
+- **Return type**: `CodeIgniter\HTTP\Message`
+
+Adds a value to an existing header. The header must already be an array of values instead of a single string. If it is a string then a LogicException will be thrown.
+
+```php
+appendHeader('Accept-Language', 'en-US; q=0.8');
+```
+
+#### prependHeader($name, $value)
+
+- **Parameters**
+ - **$name** `string` The name of the header to modify
+ - **$value** `string` The value to prepend to the header.
+
+- **Returns**: The current message instance
+
+- **Return type**: `CodeIgniter\HTTP\Message`
+
+Prepends a value to an existing header. The header must already be an array of values instead of a single string. If it is a string then a LogicException will be thrown.
+
+```php
+prependHeader('Accept-Language', 'en,');
+```
+
+#### addHeader($name, $value)
+
+!!! success "Available from version 4.5.0"
+
+- **Parameters**
+ - **$name** `string` The name of the header to add.
+ - **$value** `string` The value of the header.
+
+- **Returns**: The current message instance
+
+- **Return type**: `CodeIgniter\HTTP\Message`
+
+Adds a header (not a header value) with the same name. Use this only when you set multiple headers with the same name:
+
+```php
+addHeader('Set-Cookie', 'logged_in=no; Path=/');
+$message->addHeader('Set-Cookie', 'sessid=123456; Path=/');
+```
+
+#### getProtocolVersion()
+
+- **Returns**: The current HTTP protocol version
+- **Return type**: `string`
+
+Returns the message's current HTTP protocol. If none has been set, it will return `1.1`.
+
+#### setProtocolVersion($version)
+
+- **Parameters**
+ - **$version** `string` The HTTP protocol version
+
+- **Returns**: The current message instance
+
+- **Return type**: `CodeIgniter\HTTP\Message`
+
+Sets the HTTP protocol version this Message uses. Valid values are `1.0`, `1.1`, `2.0` and `3.0`:
+
+```php
+setProtocolVersion('1.1');
+```
diff --git a/docs/incoming/methodspoofing.md b/docs/incoming/methodspoofing.md
new file mode 100644
index 000000000000..87b927397d36
--- /dev/null
+++ b/docs/incoming/methodspoofing.md
@@ -0,0 +1,18 @@
+# HTTP Method Spoofing
+
+When working with HTML forms, you can only use GET or POST HTTP verbs. In most cases, this is just fine. However, to support RESTful routing, you need to support other, more correct, verbs, like DELETE or PUT. Since browsers don't support this, CodeIgniter provides you with a way to spoof the method that is being used. This allows you to make a POST request, but tell the application that it should be treated as a different request type.
+
+To spoof the method, a hidden input is added to the form with the name of `_method`. Its value is the HTTP verb that you want the request to be:
+
+```
+
+```
+
+This form is converted into a PUT request and is a true PUT request as far as the routing and the IncomingRequest class are concerned.
+
+The form that you are using must be a POST request. GET requests cannot be spoofed.
+
+!!! note "Note"
+ Be sure to check your web server's configuration as some servers do not support all HTTP verbs with the default configuration, and must have additional packages enabled to work.
diff --git a/docs/incoming/request.md b/docs/incoming/request.md
new file mode 100644
index 000000000000..5c7fcf2e02f0
--- /dev/null
+++ b/docs/incoming/request.md
@@ -0,0 +1,181 @@
+# Request Class
+
+The request class is an object-oriented representation of an HTTP request. This is meant to work for both incoming, such as a request to the application from a browser, and outgoing requests, like those used to send a request from the application to a third-party application.
+
+This class provides the common functionality they both need, but both cases have custom classes that extend from the Request class to add specific functionality. In practice, you will need to use these classes.
+
+See the documentation for the [IncomingRequest Class](incomingrequest.md) and [CURLRequest Class](../libraries/curlrequest.md) for more usage details.
+
+## Class Reference
+
+## CodeIgniter\HTTP
+
+### Request
+
+#### getIPAddress()
+
+- **Returns**: The user's IP address, if it can be detected. If the IP address
+ is not a valid IP address, it will return `0.0.0.0`.
+
+- **Return type**: `string`
+
+Returns the IP address for the current user. If the IP address is not valid, the method will return `0.0.0.0`:
+
+```php
+getIPAddress();
+```
+
+!!! important "Important"
+ This method takes into account the `Config\App::$proxyIPs` setting and will return the client IP address reported by the HTTP header for the allowed IP address.
+
+#### isValidIP($ip[, $which = ''])
+
+!!! danger "Deprecated since version 4.0.5"
+ Use [Validation](../libraries/validation.md) instead.
+
+!!! important "Important"
+ This method is deprecated. It will be removed in future releases.
+
+- **Parameters**
+ - **$ip** `string` IP address
+ - **$which** `string` IP protocol (`ipv4` or `ipv6`)
+
+- **Returns**: true if the address is valid, false if not
+
+- **Return type**: `bool`
+
+Takes an IP address as input and returns true or false (boolean) depending on whether it is valid or not.
+
+!!! note "Note"
+ The `$request->getIPAddress()` method above automatically validates the IP address.
+
+ ```php
+ isValidIP($ip)) {
+ echo 'Not Valid';
+ } else {
+ echo 'Valid';
+ }
+ ```
+
+Accepts an optional second string parameter of `ipv4` or `ipv6` to specify an IP format. The default checks for both formats.
+
+#### getMethod()
+
+- **Returns**: HTTP request method
+- **Return type**: `string`
+
+Returns the `$_SERVER['REQUEST_METHOD']`.
+
+```php
+getMethod(); // Outputs: POST
+```
+
+#### setMethod($method)
+
+!!! danger "Deprecated since version 4.0.5"
+ Use `CodeIgniter\HTTP\Request::withMethod()` instead.
+
+- **Parameters**
+ - **$method** `string` Sets the request method. Used when spoofing the request.
+
+- **Returns**: This request
+
+- **Return type**: `Request`
+
+#### withMethod($method)
+
+!!! success "Available from version 4.0.5"
+
+- **Parameters**
+ - **$method** `string` Sets the request method.
+
+- **Returns**: New request instance
+
+- **Return type**: `Request`
+
+#### getServer([$index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$index** `mixed` Value name
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in the [PHP manual](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int|array` Flags to apply. A list of flags can be found in the [PHP manual](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_SERVER` item value if found, null if not
+
+- **Return type**: `mixed`
+
+This method is identical to the `getPost()`, `getGet()` and `getCookie()` methods from the [IncomingRequest Class](incomingrequest.md), only it fetches server data (`$_SERVER`):
+
+```php
+getServer('some_data');
+```
+
+To return an array of multiple `$_SERVER` values, pass all the required keys as an array.
+
+```php
+getServer(['SERVER_PROTOCOL', 'REQUEST_URI']);
+```
+
+#### getEnv([$index = null[, $filter = null[, $flags = null]]])
+
+!!! danger "Deprecated since version 4.4.4"
+ This method has never worked. Use `env()` instead.
+
+- **Parameters**
+ - **$index** `mixed` Value name
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in the [PHP manual](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int|array` Flags to apply. A list of flags can be found in the [PHP manual](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Returns**: `$_ENV` item value if found, null if not
+
+- **Return type**: `mixed`
+
+This method is identical to the `getPost()`, `getGet()` and `getCookie()` methods from the [IncomingRequest Class](incomingrequest.md), only it fetches env data (`$_ENV`):
+
+```php
+getEnv('some_data');
+```
+
+To return an array of multiple `$_ENV` values, pass all the required keys as an array.
+
+```php
+getEnv(['CI_ENVIRONMENT', 'S3_BUCKET']);
+```
+
+#### setGlobal($method, $value)
+
+- **Parameters**
+ - **$method** `string` Method name
+ - **$value** `mixed` Data to be added
+
+- **Returns**: This request
+
+- **Return type**: `Request`
+
+Allows manually setting the value of a PHP global, like `$_GET`, `$_POST`, etc.
+
+#### fetchGlobal($method [, $index = null[, $filter = null[, $flags = null]]])
+
+- **Parameters**
+ - **$method** `string` Input filter constant
+ - **$index** `mixed` Value name
+ - **$filter** `int` The type of filter to apply. A list of filters can be found in the [PHP manual](https://www.php.net/manual/en/filters.php).
+ - **$flags** `int|array` Flags to apply. A list of flags can be found in the [PHP manual](https://www.php.net/manual/en/filter.constants.php#filter.constants.flags.generic).
+
+- **Return type**: `mixed`
+
+Fetches one or more items from a global, like cookies, get, post, etc. Can optionally filter the input when you retrieve it by passing in a filter.
diff --git a/docs/incoming/restful.md b/docs/incoming/restful.md
new file mode 100644
index 000000000000..1ea44febd77d
--- /dev/null
+++ b/docs/incoming/restful.md
@@ -0,0 +1,301 @@
+# RESTful Resource Handling
+
+Representational State Transfer (REST) is an architectural style for distributed applications, first described by Roy Fielding in his 2000 PhD dissertation, [Architectural Styles and the Design of Network-based Software Architectures](https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm). That might be a bit of a dry read, and you might find Martin Fowler's [Richardson Maturity Model](https://martinfowler.com/articles/richardsonMaturityModel.html) a gentler introduction.
+
+REST has been interpreted, and misinterpreted, in more ways than most software architectures, and it might be easier to say that the more of Roy Fielding's principles that you embrace in an architecture, the more "RESTful" your application would be considered.
+
+CodeIgniter makes it easy to create RESTful APIs for your resources, with its resource routes and `ResourceController`.
+
+## Resource Routes
+
+You can quickly create a handful of RESTful routes for a single resource with the `resource()` method. This creates the five most common routes needed for full CRUD of a resource: create a new resource, update an existing one, list all of that resource, show a single resource, and delete a single resource. The first parameter is the resource name:
+
+```php
+resource('photos');
+
+// Equivalent to the following:
+$routes->get('photos/new', 'Photos::new');
+$routes->post('photos', 'Photos::create');
+$routes->get('photos', 'Photos::index');
+$routes->get('photos/(:segment)', 'Photos::show/$1');
+$routes->get('photos/(:segment)/edit', 'Photos::edit/$1');
+$routes->put('photos/(:segment)', 'Photos::update/$1');
+$routes->patch('photos/(:segment)', 'Photos::update/$1');
+$routes->delete('photos/(:segment)', 'Photos::delete/$1');
+```
+
+!!! note "Note"
+ The ordering above is for clarity, whereas the actual order in which RouteCollection creates the routes ensures proper route resolution.
+
+!!! important "Important"
+ The routes are matched in the order they are specified, so if you have a `photos` resource above a GET route for `photos/poll`, the `show` action's route from the resource line will be matched before the GET line. To fix this, move the GET line above the resource line so that it is matched first.
+
+The second parameter accepts an array of options that can be used to modify the routes that are generated. While these routes are geared toward API usage, where more methods are allowed, you can pass in the `websafe` option to have it generate update and delete methods that work with HTML forms:
+
+```php
+resource('photos', ['websafe' => 1]);
+
+// The following equivalent routes are created:
+$routes->post('photos/(:segment)/delete', 'Photos::delete/$1');
+$routes->post('photos/(:segment)', 'Photos::update/$1');
+```
+
+### Change the Controller Used
+
+You can specify the controller that should be used by passing in the `controller` option with the name of the controller that should be used:
+
+```php
+resource('photos', ['controller' => 'Gallery']);
+// Would create routes like:
+$routes->get('photos', '\App\Controllers\Gallery::index');
+```
+
+```php
+resource('photos', ['controller' => '\App\Gallery']);
+// Would create routes like:
+$routes->get('photos', '\App\Gallery::index');
+```
+
+```php
+resource('photos', ['namespace' => '', 'controller' => Gallery::class]);
+// Would create routes like:
+$routes->get('photos', '\App\Controllers\Gallery::index');
+```
+
+See also [Controller's Namespace](routing.md#controllers-namespace).
+
+### Change the Placeholder Used
+
+By default, the `(:segment)` placeholder is used when a resource ID is needed. You can change this by passing in the `placeholder` option with the new string to use:
+
+```php
+resource('photos', ['placeholder' => '(:num)']);
+
+// Generates routes like:
+$routes->get('photos/(:num)', 'Photos::show/$1');
+```
+
+### Limit the Routes Made
+
+You can restrict the routes generated with the `only` option. This should be **an array** or **comma-separated list** of method names that should be created. Only routes that match one of these methods will be created. The rest will be ignored:
+
+```php
+resource('photos', ['only' => ['index', 'show']]);
+```
+
+Otherwise you can remove unused routes with the `except` option. This should also be **an array** or **comma-separated list** of method names. This option runs after `only`:
+
+```php
+resource('photos', ['except' => 'new,edit']);
+```
+
+Valid methods are: `index`, `show`, `create`, `update`, `new`, `edit` and `delete`.
+
+## ResourceController
+
+The `ResourceController` provides a convenient starting point for your RESTful API, with methods that correspond to the resource routes above.
+
+Extend it, overriding the `modelName` and `format` properties, and then implement those methods that you want handled:
+
+```php
+respond($this->model->findAll());
+ }
+
+ // ...
+}
+```
+
+The routing for this would be:
+
+```php
+resource('photos');
+```
+
+## Presenter Routes
+
+You can quickly create a presentation controller which aligns with a resource controller, using the `presenter()` method. This creates routes for the controller methods that would return views for your resource, or process forms submitted from those views.
+
+It is not needed, since the presentation can be handled with a conventional controller - it is a convenience. Its usage is similar to the resource routing:
+
+```php
+presenter('photos');
+
+// Equivalent to the following:
+$routes->get('photos/new', 'Photos::new');
+$routes->post('photos/create', 'Photos::create');
+$routes->post('photos', 'Photos::create'); // alias
+$routes->get('photos', 'Photos::index');
+$routes->get('photos/show/(:segment)', 'Photos::show/$1');
+$routes->get('photos/(:segment)', 'Photos::show/$1'); // alias
+$routes->get('photos/edit/(:segment)', 'Photos::edit/$1');
+$routes->post('photos/update/(:segment)', 'Photos::update/$1');
+$routes->get('photos/remove/(:segment)', 'Photos::remove/$1');
+$routes->post('photos/delete/(:segment)', 'Photos::delete/$1');
+```
+
+!!! note "Note"
+ The ordering above is for clarity, whereas the actual order in which RouteCollection creates the routes ensures proper route resolution.
+
+You would not have routes for `photos` for both a resource and a presenter controller. You need to distinguish them, for instance:
+
+```php
+resource('api/photo');
+$routes->presenter('admin/photos');
+```
+
+The second parameter accepts an array of options that can be used to modify the routes that are generated.
+
+### Change the Controller Used
+
+You can specify the controller that should be used by passing in the `controller` option with the name of the controller that should be used:
+
+```php
+presenter('photos', ['controller' => 'Gallery']);
+// Would create routes like:
+$routes->get('photos', '\App\Controllers\Gallery::index');
+```
+
+```php
+presenter('photos', ['controller' => '\App\Gallery']);
+// Would create routes like:
+$routes->get('photos', '\App\Gallery::index');
+```
+
+```php
+presenter('photos', ['namespace' => '', 'controller' => Gallery::class]);
+// Would create routes like:
+$routes->get('photos', '\App\Controllers\Gallery::index');
+```
+
+See also [Controller's Namespace](routing.md#controllers-namespace).
+
+### Change the Placeholder Used
+
+By default, the `(:segment)` placeholder is used when a resource ID is needed. You can change this by passing in the `placeholder` option with the new string to use:
+
+```php
+presenter('photos', ['placeholder' => '(:num)']);
+
+// Generates routes like:
+$routes->get('photos/(:num)', 'Photos::show/$1');
+```
+
+### Limit the Routes Made
+
+You can restrict the routes generated with the `only` option. This should be **an array** or **comma-separated list** of method names that should be created. Only routes that match one of these methods will be created. The rest will be ignored:
+
+```php
+presenter('photos', ['only' => ['index', 'show']]);
+```
+
+Otherwise you can remove unused routes with the `except` option. This should also be **an array** or **comma-separated list** of method names. This option runs after `only`:
+
+```php
+presenter('photos', ['except' => 'new,edit']);
+```
+
+Valid methods are: `index`, `show`, `new`, `create`, `edit`, `update`, `remove` and `delete`.
+
+## ResourcePresenter
+
+The `ResourcePresenter` provides a convenient starting point for presenting views of your resource, and processing data from forms in those views, with methods that align with the resource routes above.
+
+Extend it, overriding the `modelName` property, and then implement those methods that you want handled:
+
+```php
+model->findAll());
+ }
+
+ // ...
+}
+```
+
+The routing for this would be:
+
+```php
+presenter('photos');
+```
+
+## Presenter/Controller Comparison
+
+This table presents a comparison of the default routes created by `resource()` and `presenter()` with their corresponding Controller functions.
+
+| Operation | Method | Controller Route | Presenter Route | Controller Function | Presenter Function |
+|----|----|----|----|----|----|
+| **New** | GET | photos/new | photos/new | `new()` | `new()` |
+| **Create** | POST | photos | photos | `create()` | `create()` |
+| Create (alias) | POST | | photos/create | | `create()` |
+| **List** | GET | photos | photos | `index()` | `index()` |
+| **Show** | GET | photos/(:segment) | photos/(:segment) | `show($id = null)` | `show($id = null)` |
+| Show (alias) | GET | | photos/show/(:segment) | | `show($id = null)` |
+| **Edit** | GET | photos/(:segment)/edit | photos/edit/(:segment) | `edit($id = null)` | `edit($id = null)` |
+| **Update** | PUT/PATCH | photos/(:segment) | | `update($id = null)` | |
+| Update (websafe) | POST | photos/(:segment) | photos/update/(:segment) | `update($id = null)` | `update($id = null)` |
+| **Remove** | GET | | photos/remove/(:segment) | | `remove($id = null)` |
+| **Delete** | DELETE | photos/(:segment) | | `delete($id = null)` | |
+| Delete (websafe) | POST | photos/(:segment)/delete | photos/delete/(:segment) | `delete($id = null)` | `delete($id = null)` |
diff --git a/docs/incoming/routing.md b/docs/incoming/routing.md
new file mode 100644
index 000000000000..0df5bac7ef1f
--- /dev/null
+++ b/docs/incoming/routing.md
@@ -0,0 +1,1301 @@
+# URI Routing
+
+## What is URI Routing?
+
+URI Routing associates a URI with a controller's method.
+
+CodeIgniter has two kinds of routing. One is **Defined Route Routing**, and the other is **Auto Routing**. With Defined Route Routing, you can define routes manually. It allows flexible URLs. Auto Routing automatically routes HTTP requests based on conventions and executes the corresponding controller methods. There is no need to define routes manually.
+
+First, let's look at Defined Route Routing. If you want to use Auto Routing, see [Auto Routing (Improved)](auto_routing_improved.md#auto-routing-improved).
+
+## Setting Routing Rules
+
+Routing rules are defined in the **app/Config/Routes.php** file. In it you'll see that it creates an instance of the RouteCollection class (`$routes`) that permits you to specify your own routing criteria. Routes can be specified using placeholders or Regular Expressions.
+
+When you specify a route, you choose a method corresponding to HTTP verbs (request method). If you expect a GET request, you use the `get()` method:
+
+```php
+get('/', 'Home::index');
+```
+
+A route takes the **Route Path** (the URI path relative to the BaseURL, here `/`) on the left, and maps it to the **Route Handler** (the controller and method, here `Home::index`) on the right, along with any parameters that should be passed to the controller.
+
+The controller and method should be listed in the same way that you would use a static method, by separating the class and its method with a double-colon, like `Users::list`.
+
+If that method requires parameters to be passed to it, then they would be listed after the method name, separated by forward slashes:
+
+```php
+list()
+$routes->get('users', 'Users::list');
+
+// Calls $Users->list(1, 23)
+$routes->get('users/1/23', 'Users::list/1/23');
+```
+
+### Examples
+
+Here are a few basic routing examples.
+
+A URL containing the word **journals** in the first segment will be mapped to the `\App\Controllers\Blogs` class, and the [default method](#default-method), which is usually `index()`:
+
+```php
+get('journals', 'Blogs');
+```
+
+A URL containing the segments **blog/joe** will be mapped to the `\App\Controllers\Blogs` class and the `users()` method. The ID will be set to `34`:
+
+```php
+get('blog/joe', 'Blogs::users/34');
+```
+
+A URL with **product** as the first segment and anything in the second will be mapped to the `\App\Controllers\Catalog` class and the `productLookup()` method:
+
+```php
+get('product/(:segment)', 'Catalog::productLookup');
+```
+
+A URL with **product** as the first segment and a number in the second will be mapped to the `\App\Controllers\Catalog` class and the `productLookupByID()` method, passing in the match as a variable to the method:
+
+```php
+get('product/(:num)', 'Catalog::productLookupByID/$1');
+```
+
+### HTTP verb Routes
+
+You can use any standard HTTP verb (GET, POST, PUT, DELETE, OPTIONS, etc.):
+
+```php
+post('products', 'Product::feature');
+$routes->put('products/1', 'Product::feature');
+$routes->delete('products/1', 'Product::feature');
+```
+
+You can supply multiple verbs that a route should match by passing them in as an array to the `match()` method:
+
+```php
+match(['GET', 'PUT'], 'products', 'Product::feature');
+```
+
+### Specifying Route Handlers
+
+#### Controller's Namespace
+
+When you specify a controller and method name as a string, if a controller is written without a leading `\`, the [Default Namespace](#default-namespace) will be prepended:
+
+```php
+post('api/users', 'Api\Users::update');
+```
+
+If you put `\` at the beginning, it is treated as a fully qualified class name:
+
+```php
+get('blog', '\Acme\Blog\Controllers\Home::list');
+```
+
+You can also specify the namespace with the `namespace` option:
+
+```php
+get('admin/users', 'Users::index', ['namespace' => 'Admin']);
+```
+
+See [Assigning Namespace](#assigning-namespace) for details.
+
+#### Array Callable Syntax
+
+!!! success "Available from version 4.2.0"
+
+Since v4.2.0, you can use array callable syntax to specify the controller:
+
+```php
+$routes->get('/', [\App\Controllers\Home::class, 'index']);
+```
+
+Or using the `use` keyword:
+
+```php
+use App\Controllers\Home;
+
+$routes->get('/', [Home::class, 'index']);
+```
+
+If you forget to add `use App\Controllers\Home;`, the controller classname is interpreted as `\Home`, not `App\Controllers\Home`.
+
+!!! note "Note"
+ When you use Array Callable Syntax, the classname is always interpreted as a fully qualified classname. So [Default Namespace](#default-namespace) and [namespace option](#assigning-namespace) have no effect.
+
+##### Array Callable Syntax and Placeholders
+
+If there are placeholders, it will automatically set the parameters in the specified order:
+
+```php
+use App\Controllers\Product;
+
+$routes->get('product/(:num)/(:num)', [Product::class, 'index']);
+
+// The above code is the same as the following:
+$routes->get('product/(:num)/(:num)', 'Product::index/$1/$2');
+```
+
+But the auto-configured parameters may not be correct if you use regular expressions in routes. In such a case, you can specify the parameters manually:
+
+```php
+use App\Controllers\Product;
+
+$routes->get('product/(:num)/(:num)', [[Product::class, 'index'], '$2/$1']);
+
+// The above code is the same as the following:
+$routes->get('product/(:num)/(:num)', 'Product::index/$2/$1');
+```
+
+#### Using Closures
+
+You can use an anonymous function, or Closure, as the destination that a route maps to. This function will be executed when the user visits that URI. This is handy for quickly executing small tasks, or even just showing a simple view:
+
+```php
+get('feed', static function () {
+ $rss = new RSSFeeder();
+
+ return $rss->feed('general');
+});
+```
+
+### Specifying Route Paths
+
+#### Placeholders
+
+A typical route might look something like this:
+
+```php
+get('product/(:num)', 'Catalog::productLookup');
+```
+
+In a route, the first parameter contains the URI to be matched, while the second parameter contains the destination it should be routed to. In the above example, if the literal word "product" is found in the first segment of the URL path, and a number is found in the second segment, the `Catalog` class and the `productLookup` method are used instead.
+
+Placeholders are simply strings that represent a Regular Expression pattern. During the routing process, these placeholders are replaced with the value of the Regular Expression. They are primarily used for readability.
+
+The following placeholders are available for you to use in your routes:
+
+| Placeholders | Description |
+|----|----|
+| (:any) | will match all characters from that point to the end of the URI. This may include multiple URI segments. |
+| (:segment) | will match any character except for a forward slash (`/`) restricting the result to a single segment. |
+| (:num) | will match any positive integer. |
+| (:alpha) | will match any string of alphabetic characters. |
+| (:alphanum) | will match any string of alphabetic characters or integers, or any combination of the two. |
+| (:hash) | is the same as `(:segment)`, but can be used to easily see which routes use hashed ids. |
+
+!!! note "Note"
+ `{locale}` cannot be used as a placeholder or other part of the route, as it is reserved for use in [localization](../outgoing/localization.md).
+
+##### The Behavior of (:any)
+
+Note that a single `(:any)` will match multiple segments in the URL if present.
+
+For example, the route:
+
+```php
+get('product/(:any)', 'Catalog::productLookup/$1');
+```
+
+will match **product/123**, **product/123/456**, **product/123/456/789** and so on.
+
+By default, in the above example, if the `$1` placeholder contains a slash (`/`), it will still be split into multiple parameters when passed to `Catalog::productLookup()`.
+
+!!! note "Note"
+ Since v4.5.0, you can change the behavior with the config option. See [Multiple URI Segments as One Parameter](#multiple-uri-segments-as-one-parameter) for details.
+
+The implementation in the Controller should take into account the maximum parameters:
+
+```php
+get('product/(:segment)', 'Catalog::productLookup/$1');
+```
+
+will only match **product/123** and generate 404 errors for the other examples.
+
+#### Custom Placeholders
+
+You can create your own placeholders that can be used in your routes file to fully customize the experience and readability.
+
+You add new placeholders with the `addPlaceholder()` method. The first parameter is the string to be used as the placeholder. The second parameter is the Regular Expression pattern it should be replaced with. This must be called before you add the route:
+
+```php
+addPlaceholder('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}');
+$routes->get('users/(:uuid)', 'Users::show/$1');
+```
+
+#### Regular Expressions
+
+If you prefer, you can use regular expressions to define your routing rules. Any valid regular expression is allowed, as are back-references.
+
+!!! important "Important"
+ If you use back-references, you must use the dollar syntax rather than the double backslash syntax. A typical RegEx route might look something like this:
+
+ ```php
+ get('products/([a-z]+)/(\d+)', 'Products::show/$1/id_$2');
+ ```
+
+In the above example, a URI similar to **products/shirts/123** would instead call the `show()` method of the `Products` controller class, with the original first and second segments passed as arguments to it.
+
+With regular expressions, you can also catch a segment containing a forward slash (`/`), which would usually represent the delimiter between multiple segments.
+
+For example, if a user accesses a password-protected area of your web application and you wish to be able to redirect them back to the same page after they log in, you may find this example useful:
+
+```php
+get('login/(.+)', 'Auth::login/$1');
+```
+
+By default, in the above example, if the `$1` placeholder contains a slash (`/`), it will still be split into multiple parameters when passed to `Auth::login()`.
+
+!!! note "Note"
+ Since v4.5.0, you can change the behavior with the config option. See [Multiple URI Segments as One Parameter](#multiple-uri-segments-as-one-parameter) for details.
+
+For those of you who don't know regular expressions and want to learn more about them, [regular-expressions.info](https://www.regular-expressions.info/) might be a good starting point.
+
+!!! note "Note"
+ You can also mix and match placeholders with regular expressions.
+
+### View Routes
+
+!!! success "Available from version 4.3.0"
+
+If you just want to render a view that has no logic associated with it, you can use the `view()` method. This is always treated as a GET request. This method accepts the name of the view to load as the second parameter.
+
+```php
+view('about', 'pages/about');
+```
+
+If you use placeholders within your route, you can access them within the view in a special variable, `$segments`. They are available as an array, indexed in the order they appear in the route.
+
+```php
+view('map/(:segment)/(:segment)', 'map');
+
+// Within the view, you can access the segments with
+// $segments[0] and $segments[1] respectively.
+```
+
+### Redirecting Routes
+
+Any site that lives long enough is bound to have pages that move. You can specify routes that should redirect to other routes with the `addRedirect()` method. The first parameter is the URI pattern for the old route. The second parameter is either the new URI to redirect to, or the name of a named route. The third parameter is the HTTP status code that should be sent along with the redirect. The default value is `302`, which is a temporary redirect and is recommended in most cases:
+
+```php
+get('users/profile', 'Users::profile', ['as' => 'profile']);
+
+// Redirect to a named route
+$routes->addRedirect('users/about', 'profile');
+// Redirect to a URI
+$routes->addRedirect('users/about', 'users/profile');
+
+// Redirect with placeholder
+$routes->get('post/(:num)/comment/(:num)', 'PostsComments::index', ['as' => 'post.comment']);
+
+// Redirect to a URI
+$routes->addRedirect('article/(:num)/(:num)', 'post/$1/comment/$2');
+// Redirect to a named route
+$routes->addRedirect('article/(:num)/(:num)', 'post.comment');
+```
+
+!!! note "Note"
+ Since v4.2.0, `addRedirect()` can use placeholders.
+
+If a redirect route is matched during a page load, the user will be immediately redirected to the new page before a controller can be loaded.
+
+### Environment Restrictions
+
+You can create a set of routes that will only be viewable in a certain environment. This allows you to create tools that only the developer can use on their local machines that are not reachable on testing or production servers. This can be done with the `environment()` method. The first parameter is the name of the environment. Any routes defined within this closure are only accessible from the given environment:
+
+```php
+environment('development', static function ($routes) {
+ $routes->get('builder', 'Tools\Builder::index');
+});
+```
+
+### Routes with any HTTP verbs
+
+!!! important "Important"
+ This method exists only for backward compatibility. Do not use it in new projects. Even if you are already using it, we recommend that you use another, more appropriate method.
+
+!!! warning "Warning"
+ While the `add()` method seems to be convenient, it is recommended to always use the HTTP-verb-based routes, described above, as it is more secure. If you use the [CSRF protection](../libraries/security.md), it does not protect **GET** requests. If the URI specified in the `add()` method is accessible by the GET method, the CSRF protection will not work.
+
+It is possible to define a route with any HTTP verb. You can use the `add()` method:
+
+```php
+add('products', 'Product::feature');
+```
+
+!!! note "Note"
+ Using the HTTP-verb-based routes will also provide a slight performance increase, since only routes that match the current request method are stored, resulting in fewer routes to scan through when trying to find a match.
+
+### Mapping Multiple Routes
+
+!!! important "Important"
+ This method exists only for backward compatibility. Do not use it in new projects. Even if you are already using it, we recommend that you use another, more appropriate method.
+
+!!! warning "Warning"
+ Like `add()`, the `map()` method is not recommended because it calls `add()` internally.
+
+While the `add()` method is simple to use, it is often handier to work with multiple routes at once, using the `map()` method. Instead of calling the `add()` method for each route that you need to add, you can define an array of routes and then pass it as the first parameter to the `map()` method:
+
+```php
+ 'Catalog::productLookupById',
+ 'product/(:alphanum)' => 'Catalog::productLookupByName',
+];
+
+$routes->map($multipleRoutes);
+```
+
+### Command-Line Only Routes
+
+!!! note "Note"
+ It is recommended to use Spark Commands for CLI scripts instead of calling controllers via CLI. See the [Creating Spark Commands](../cli/cli_commands.md) page for detailed information.
+
+Any route created by any of the HTTP-verb-based route methods will also be inaccessible from the CLI, but routes created by the `add()` method will still be available from the command line.
+
+You can create routes that work only from the command line, and are inaccessible from the web browser, with the `cli()` method:
+
+```php
+cli('migrate', 'App\Database::migrate');
+```
+
+!!! warning "Warning"
+ If you enable [Auto Routing (Legacy)](#auto-routing-legacy) and place the command file in **app/Controllers**, anyone could access the command with the help of Auto Routing (Legacy) via HTTP.
+
+## Global Options
+
+All of the methods for creating a route (`get()`, `post()`, [resource()](restful.md), etc.) can take an array of options that can modify the generated routes, or further restrict them. The `$options` array is always the last parameter:
+
+```php
+add('from', 'to', $options);
+$routes->get('from', 'to', $options);
+$routes->post('from', 'to', $options);
+$routes->put('from', 'to', $options);
+$routes->head('from', 'to', $options);
+$routes->options('from', 'to', $options);
+$routes->delete('from', 'to', $options);
+$routes->patch('from', 'to', $options);
+$routes->match(['GET', 'PUT'], 'from', 'to', $options);
+$routes->resource('photos', $options);
+$routes->map($array, $options);
+$routes->group('name', $options, static function () {});
+```
+
+### Applying Filters
+
+You can alter the behavior of specific routes by supplying filters to run before or after the controller. This is especially handy during authentication or API logging.
+
+The value for the filter can be a string or an array of strings:
+
+- matching the aliases defined in **app/Config/Filters.php**.
+- filter classnames
+
+See [Controller Filters](filters.md#aliases) for more information on defining aliases.
+
+!!! warning "Warning"
+ If you set filters to routes in **app/Config/Routes.php** (not in **app/Config/Filters.php**), it is recommended to disable Auto Routing (Legacy). When [Auto Routing (Legacy)](#auto-routing-legacy) is enabled, it may be possible that a controller can be accessed via a different URL than the configured route, in which case the filter you specified for the route will not be applied. See [Use Defined Routes Only](#use-defined-routes-only) to disable auto-routing.
+
+#### Alias Filter
+
+You specify an alias [defined in app/Config/Filters.php](filters.md#aliases) for the filter value:
+
+```php
+get('admin', ' AdminController::index', ['filter' => 'admin-auth']);
+```
+
+You may also supply arguments to be passed to the alias filter's `before()` and `after()` methods:
+
+```php
+post('users/delete/(:segment)', 'AdminController::index', ['filter' => 'admin-auth:dual,noreturn']);
+```
+
+#### Classname Filter
+
+!!! success "Available from version 4.1.5"
+
+You can specify a filter classname for the filter value:
+
+```php
+get('admin', ' AdminController::index', ['filter' => \App\Filters\SomeFilter::class]);
+```
+
+#### Multiple Filters
+
+!!! success "Available from version 4.1.5"
+
+!!! important "Important"
+ Since v4.5.0, *Multiple Filters* are always enabled. Prior to v4.5.0, *Multiple Filters* were disabled by default. If you want to use them prior to v4.5.0, see [Upgrading from 4.1.4 to 4.1.5](../installation/upgrade_415.md#multiple-filters-for-a-route) for the details.
+
+You can specify an array for the filter value:
+
+```php
+get('admin', ' AdminController::index', ['filter' => ['admin-auth', \App\Filters\SomeFilter::class]]);
+```
+
+##### Filter Arguments
+
+Additional arguments may be passed to a filter:
+
+```php
+add('users/delete/(:segment)', 'AdminController::index', ['filter' => 'admin-auth:dual,noreturn']);
+```
+
+In this example, the array `['dual', 'noreturn']` will be passed in `$arguments` to the filter's `before()` and `after()` implementation methods.
+
+### Assigning Namespace
+
+While a [Default Namespace](#default-namespace) will be prepended to the generated controllers, you can also specify a different namespace to be used in any options array, with the `namespace` option. The value should be the namespace you want modified:
+
+```php
+get('admin/users', 'Users::index', ['namespace' => 'Admin']);
+```
+
+The new namespace is only applied during that call for any methods that create a single route, like get, post, etc. For any methods that create multiple routes, the new namespace is attached to all routes generated by that function or, in the case of `group()`, all routes generated while in the closure.
+
+### Limit to Hostname
+
+You can restrict groups of routes to function only in certain domains or sub-domains of your application by passing the "hostname" option along with the desired domain to allow it on as part of the options array:
+
+```php
+get('from', 'to', ['hostname' => 'accounts.example.com']);
+```
+
+This example would only allow the specified hosts to work if the domain exactly matched **accounts.example.com**. It would not work under the main site at **example.com**.
+
+!!! note "Note"
+ The `hostname` option is compared against the full `HTTP_HOST` value of the request, including the port when one is present. When your application is served on a non-standard port (for example `localhost:8080`, behind Docker, or when using `php spark serve`), you must include the port in the `hostname` option for the routes to match. For example, `['hostname' => 'localhost:8080']` matches only requests to `localhost:8080`.
+
+#### Restrict by Multiple Hostnames
+
+!!! success "Available from version 4.6.0"
+
+You can also restrict by multiple hostnames, e.g.:
+
+```php
+get('from', 'to', ['hostname' => ['s1.example.com', 's2.example.com']]);
+```
+
+### Limit to Subdomains
+
+When the `subdomain` option is present, the system will restrict the routes to only be available on that sub-domain. The route will only be matched if the subdomain is the one the application is being viewed through:
+
+```php
+get('from', 'to', ['subdomain' => 'media']);
+```
+
+You can restrict it to any subdomain by setting the value to an asterisk (`*`). If you are viewing from a URL that does not have any subdomain present, this will not be matched:
+
+```php
+get('from', 'to', ['subdomain' => '*']);
+```
+
+!!! important "Important"
+ The system is not perfect and should be tested for your specific domain before being used in production. Most domains should work fine, but some edge cases, especially with a period in the domain itself (not used to separate suffixes or www) can potentially lead to false positives.
+
+### Offsetting the Matched Parameters
+
+You can offset the matched parameters in your route by any numeric value with the `offset` option, with the value being the number of segments to offset.
+
+This can be beneficial when developing APIs with the first URI segment being the version number. It can also be used when the first parameter is a language string:
+
+```php
+get('users/(:num)', 'users/show/$1', ['offset' => 1]);
+
+// Creates:
+$routes['users/(:num)'] = 'users/show/$2';
+```
+
+## Reverse Routing
+
+Reverse routing allows you to define the controller and method, as well as any parameters, that a link should go to, and have the router look up the current route to it. This allows route definitions to change without you having to update your application code. This is typically used within views to create links.
+
+For example, if you have a route to a photo gallery that you want to link to, you can use the `url_to()` helper function to get the route that should be used. The first parameter is the fully qualified Controller and method, separated by a double colon (`::`), much like you would use when writing the initial route itself. Any parameters that should be passed to the route are passed in next:
+
+```php
+get('users/(:num)/gallery/(:num)', 'Galleries::showUserGallery/$1/$2');
+
+?>
+
+
+View Gallery
+
+```
+
+## Named Routes
+
+You can name routes to make your application less fragile. This applies a name to a route that can be called later, and even if the route definition changes, all of the links in your application built with `url_to()` will still work without you having to make any changes. A route is named by passing in the `as` option with the name of the route:
+
+```php
+get('users/(:num)/gallery/(:num)', 'Galleries::showUserGallery/$1/$2', ['as' => 'user_gallery']);
+
+?>
+
+
+View Gallery
+
+```
+
+This has the added benefit of making the views more readable, too.
+
+!!! note "Note"
+ By default, all defined routes have names matching their paths, with placeholders replaced by their corresponding regular expressions. For example, if you define a route like `$routes->get('edit/(:num)', 'PostController::edit/$1');`, you can generate the corresponding URL using `route_to('edit/([0-9]+)', 12)`.
+
+!!! warning "Warning"
+ According to [Route Priority](#route-priority), if an unnamed route is defined first (e.g., `$routes->get('edit', 'PostController::edit');`) and another named route is defined later with the same name as the path of the first route (e.g., `$routes->get('edit/(:num)', 'PostController::edit/$1', ['as' => 'edit']);`), the second route will not be registered because its name will conflict with the automatically assigned name of the first route.
+
+## Grouping Routes
+
+You can group your routes under a common name with the `group()` method. The group name becomes a segment that appears prior to the routes defined inside of the group. This allows you to reduce the typing needed to build out an extensive set of routes that all share the opening string, like when building an admin area:
+
+```php
+group('admin', static function ($routes) {
+ $routes->get('users', 'Admin\Users::index');
+ $routes->get('blog', 'Admin\Blog::index');
+});
+```
+
+This would prefix the **users** and **blog** URIs with **admin**, handling URLs like **admin/users** and **admin/blog**.
+
+### Setting Namespace
+
+If you need to assign options to a group, like [Assigning Namespace](#assigning-namespace), do it before the callback:
+
+```php
+group('api', ['namespace' => 'App\API\v1'], static function ($routes) {
+ $routes->resource('users');
+});
+```
+
+This would handle a resource route to the `App\API\v1\Users` controller with the **api/users** URI.
+
+### Setting Filters
+
+You can also use a specific [filter](filters.md) for a group of routes. This will always run the filter before or after the controller. This is especially handy during authentication or API logging:
+
+```php
+group('api', ['filter' => 'api-auth'], static function ($routes) {
+ $routes->resource('users');
+});
+```
+
+The value for the filter must match one of the aliases defined within **app/Config/Filters.php**.
+
+!!! note "Note"
+ Prior to v4.5.4, due to a bug, filters passed to the `group()` were not merged into the filters passed to the inner routes.
+
+### Setting Other Options
+
+At some point, you may want to group routes for the purpose of applying filters or other route config options like namespace, subdomain, etc. Without necessarily needing to add a prefix to the group, you can pass an empty string in place of the prefix and the routes in the group will be routed as though the group never existed but with the given route config options:
+
+```php
+group('', ['namespace' => 'Myth\Auth\Controllers'], static function ($routes) {
+ $routes->get('login', 'AuthController::login', ['as' => 'login']);
+ $routes->post('login', 'AuthController::attemptLogin');
+ $routes->get('logout', 'AuthController::logout');
+});
+```
+
+### Nesting Groups
+
+It is possible to nest groups within groups for finer organization if you need it:
+
+```php
+group('admin', ['filter' => 'myfilter1:config'], static function ($routes) {
+ $routes->get('/', 'Admin\Admin::index');
+
+ $routes->group('users', ['filter' => 'myfilter2:region'], static function ($routes) {
+ $routes->get('list', 'Admin\Users::list');
+ });
+});
+```
+
+This would handle the URL at **admin/users/list**.
+
+The `filter` option passed to the outer `group()` is merged with the inner `group()` filter option. The above code runs `myfilter1:config` for the route `admin`, and `myfilter1:config` and `myfilter2:region` for the route `admin/users/list`.
+
+!!! note "Note"
+ Prior to v4.6.0, the same filter could not be run multiple times with different arguments.
+
+Any other overlapping options passed to the inner `group()` will overwrite their values.
+
+!!! note "Note"
+ Prior to v4.5.0, due to a bug, options passed to the outer `group()` were not merged with the inner `group()` options.
+
+## Route Priority
+
+Routes are registered in the routing table in the order in which they are defined. This means that when a URI is accessed, the first matching route will be executed.
+
+!!! warning "Warning"
+ If a route path is defined more than once with different handlers, only the first defined route is registered.
+
+You can check registered routes in the routing table by running the [spark routes](#spark-routes) command.
+
+### Changing Route Priority
+
+When working with modules, it can be a problem if the routes in the application contain wildcards. Then the module routes will not be processed correctly. You can solve this problem by lowering the priority of route processing using the `priority` option. The parameter accepts positive integers and zero. The higher the number specified in the `priority`, the lower the route priority in the processing queue:
+
+```php
+setPrioritize();
+
+// Config\Routes
+$routes->get('(.*)', 'Posts::index', ['priority' => 1]);
+
+// Modules\Acme\Config\Routes
+$routes->get('admin', 'Admin::index');
+
+// The "admin" route will now be processed before the wildcard route.
+```
+
+To disable this functionality, you must call the method with the parameter `false`:
+
+```php
+setPrioritize(false);
+```
+
+!!! note "Note"
+ By default, all routes have a priority of 0. Negative integers will be cast to the absolute value.
+
+## Routes Configuration Options
+
+The RouteCollection class provides several options that affect all routes, and can be modified to meet your application's needs. These options are available in **app/Config/Routing.php**.
+
+!!! note "Note"
+ The config file **app/Config/Routing.php** was added in v4.4.0. In previous versions, the setter methods were used in **app/Config/Routes.php** to change settings.
+
+### Default Namespace
+
+When matching a controller to a route, the router will add the default namespace value to the front of the controller specified by the route. By default, this value is `App\Controllers`.
+
+If you set the value to an empty string (`''`), it leaves each route to specify the fully namespaced controller:
+
+```php
+get('users', 'Users::index');
+
+// Controller is \Admin\Users
+$routes->get('users', 'Admin\Users::index');
+```
+
+If your controllers are not explicitly namespaced, there is no need to change this. If you namespace your controllers, then you can change this value to save typing:
+
+```php
+setDefaultNamespace('App');
+
+// Controller is \App\Users
+$routes->get('users', 'Users::index');
+
+// Controller is \App\Admin\Users
+$routes->get('users', 'Admin\Users::index');
+```
+
+### Default Method
+
+This setting is used when the route handler only has the controller name and no method name listed. The default value is `index`.
+
+```
+// In app/Config/Routing.php
+public string $defaultMethod = 'index';
+```
+
+!!! note "Note"
+ The `$defaultMethod` is also common with Auto Routing. See [Auto Routing (Improved)](auto_routing_improved.md#default-method_1) or [Auto Routing (Legacy)](#default-method-legacy).
+
+If you define the following route:
+
+```
+$routes->get('/', 'Home');
+```
+
+the `index()` method of the `App\Controllers\Home` controller is executed when the route matches.
+
+!!! note "Note"
+ Method names beginning with `_` cannot be used as the default method. However, starting with v4.5.0, the `__invoke` method is allowed.
+
+### Translate URI Dashes
+
+This option enables you to automatically replace dashes (`-`) with underscores in the controller and method URI segments when used in Auto Routing, thus saving you additional route entries if you need to do that. This is required because the dash isn't a valid class or method name character and would cause a fatal error if you try to use it:
+
+```php
+setTranslateURIDashes(true);
+```
+
+!!! note "Note"
+ When using Auto Routing (Improved), prior to v4.4.0, if `$translateURIDashes` is true, two URIs correspond to a single controller method, one URI for dashes (e.g., **foo-bar**) and one URI for underscores (e.g., **foo_bar**). This was incorrect behavior. Since v4.4.0, the URI for underscores (**foo_bar**) is not accessible.
+
+### Use Defined Routes Only
+
+Since v4.2.0, auto-routing is disabled by default.
+
+When no defined route is found that matches the URI, the system will attempt to match that URI against the controllers and methods when Auto Routing is enabled.
+
+You can disable this automatic matching, and restrict routes to only those defined by you, by setting the `$autoRoute` property to false:
+
+```php
+setAutoRoute(false);
+```
+
+!!! warning "Warning"
+ If you use the [CSRF protection](../libraries/security.md), it does not protect **GET** requests. If the URI is accessible by the GET method, the CSRF protection will not work.
+
+### 404 Override
+
+When a page is not found that matches the current URI, the system will show a generic 404 view. Using the `$override404` property within the routing config file, you can define a controller class/method for 404 routes.
+
+```php
+set404Override('App\Errors::show404');
+
+// Will display a custom view.
+$routes->set404Override(static function () {
+ // If you want to get the URI segments.
+ $segments = request()->getUri()->getSegments();
+
+ return view('my_errors/not_found.html');
+});
+```
+
+!!! note "Note"
+ Starting with v4.5.0, the 404 Override feature sets the Response status code to `404` by default. In previous versions, the code was `200`. If you want to change the status code in the controller, see `CodeIgniter\HTTP\Response::setStatusCode()` for information on how to set the status code.
+
+### Route Processing by Priority
+
+Enables or disables processing of the routes queue by priority. Lowering the priority is defined in the route option. Disabled by default. This functionality affects all routes. For an example use of lowering the priority, see [Route Priority](#route-priority):
+
+```php
+setPrioritize();
+
+// to disable
+$routes->setPrioritize(false);
+```
+
+### Multiple URI Segments as One Parameter
+
+!!! success "Available from version 4.5.0"
+
+When this option is enabled, a placeholder that matches multiple segments, such as `(:any)`, will be passed directly as it is to one parameter, even if it contains multiple segments.
+
+```php
+get('product/(:any)', 'Catalog::productLookup/$1');
+```
+
+will match **product/123**, **product/123/456**, **product/123/456/789** and so on. And if the URI is **product/123/456**, `123/456` will be passed to the first parameter of the `Catalog::productLookup()` method.
+
+## Auto Routing (Improved)
+
+!!! success "Available from version 4.2.0"
+
+Auto Routing (Improved) is a new, more secure automatic routing system.
+
+See [Auto Routing (Improved)](auto_routing_improved.md) for details.
+
+## Auto Routing (Legacy)
+
+!!! important "Important"
+ This feature exists only for backward compatibility. Do not use it in new projects. Even if you are already using it, we recommend that you use the [Auto Routing (Improved)](auto_routing_improved.md#auto-routing-improved) instead.
+
+Auto Routing (Legacy) is a routing system from CodeIgniter 3. It can automatically route HTTP requests based on conventions and execute the corresponding controller methods.
+
+It is recommended to define all routes in the **app/Config/Routes.php** file, or to use [Auto Routing (Improved)](auto_routing_improved.md#auto-routing-improved).
+
+!!! warning "Warning"
+ To prevent misconfiguration and miscoding, we recommend that you do not use the Auto Routing (Legacy) feature. It is easy to create vulnerable apps where controller filters or CSRF protection are bypassed.
+
+!!! important "Important"
+ Auto Routing (Legacy) routes an HTTP request with **any** HTTP method to a controller method.
+
+### Enable Auto Routing (Legacy)
+
+Since v4.2.0, auto-routing is disabled by default.
+
+To use it, you need to set the `$autoRoute` option to `true` in **app/Config/Routing.php**:
+
+```
+public bool $autoRoute = true;
+```
+
+And set the property `$autoRoutesImproved` to `false` in **app/Config/Feature.php**:
+
+```
+public bool $autoRoutesImproved = false;
+```
+
+### URI Segments (Legacy)
+
+The segments in the URL, following the Model-View-Controller approach, usually represent:
+
+```
+example.com/class/method/ID
+```
+
+1. The first segment represents the controller **class** that should be invoked.
+2. The second segment represents the class **method** that should be called.
+3. The third, and any additional segments, represent the ID and any variables that will be passed to the controller.
+
+Consider this URI:
+
+```
+example.com/index.php/helloworld/index/1
+```
+
+In the above example, CodeIgniter would attempt to find a controller named **Helloworld.php** and execute the `index()` method, passing `'1'` as the first argument.
+
+See [Auto Routing (Legacy) in Controllers](controllers.md#auto-routing-legacy) for more info.
+
+### Configuration Options (Legacy)
+
+These options are available in the **app/Config/Routing.php** file.
+
+#### Default Controller (Legacy)
+
+##### For Site Root URI (Legacy)
+
+When a user visits the root of your site (i.e., **example.com**), the controller to use is determined by the value set to the `$defaultController` property, unless a route exists for it explicitly.
+
+The default value for this is `Home`, which matches the controller at **app/Controllers/Home.php**:
+
+```
+public string $defaultController = 'Home';
+```
+
+##### For Directory URI (Legacy)
+
+The default controller is also used when no matching route has been found, and the URI would point to a directory in the controllers directory. For example, if the user visits **example.com/admin**, and a controller was found at **app/Controllers/Admin/Home.php**, it would be used.
+
+See [Auto Routing (Legacy) in Controllers](controllers.md#auto-routing-legacy) for more info.
+
+#### Default Method (Legacy)
+
+This works similarly to the default controller setting, but is used to determine the default method that is used when a controller is found that matches the URI, but no segment exists for the method. The default value is `index`.
+
+In this example, if the user were to visit **example.com/products**, and a `Products` controller existed, the `Products::listAll()` method would be executed:
+
+```
+public string $defaultMethod = 'listAll';
+```
+
+## Confirming Routes
+
+CodeIgniter has the following [command](../cli/spark_commands.md) to display all routes.
+
+### spark routes
+
+Displays all routes and filters:
+
+```console
+php spark routes
+```
+
+The output is like the following:
+
+```none
++---------+---------+---------------+-------------------------------+----------------+---------------+
+| Method | Route | Name | Handler | Before Filters | After Filters |
++---------+---------+---------------+-------------------------------+----------------+---------------+
+| GET | / | » | \App\Controllers\Home::index | | toolbar |
+| GET | feed | » | (Closure) | | toolbar |
++---------+---------+---------------+-------------------------------+----------------+---------------+
+```
+
+The *Method* column shows the HTTP method that the route is listening for.
+
+The *Route* column shows the route path to match. The route of a defined route is expressed as a regular expression.
+
+Since v4.3.0, the *Name* column shows the route name. `»` indicates the name is the same as the route path.
+
+!!! important "Important"
+ The system is not perfect. For routes containing regular expression patterns like `([^/]+)` or `{locale}`, the *Filters* displayed might not be correct (if you set a complicated URI pattern for the filters in **app/Config/Filters.php**), or they are displayed as ``.
+
+ The [spark filter:check](filters.md#filtercheck) command can be used to check for 100% accurate filters.
+
+#### Auto Routing (Improved)
+
+When you use Auto Routing (Improved), the output is like the following:
+
+```none
++-----------+-------------------------+---------------+-----------------------------------+----------------+---------------+
+| Method | Route | Name | Handler | Before Filters | After Filters |
++-----------+-------------------------+---------------+-----------------------------------+----------------+---------------+
+| GET(auto) | product/list/../..[/..] | | \App\Controllers\Product::getList | | toolbar |
++-----------+-------------------------+---------------+-----------------------------------+----------------+---------------+
+```
+
+The *Method* will be like `GET(auto)`.
+
+`/..` in the *Route* column indicates one segment. `[/..]` indicates it is optional.
+
+!!! note "Note"
+ When auto-routing is enabled and you have the route `home`, it can also be accessed by `Home`, or maybe by `hOme`, `hoMe`, `HOME`, etc., but the command will show only `home`.
+
+If you see a route starting with `x` like the following, it indicates an invalid route that won't be routed, but the controller has a public method for routing.
+
+```none
++-----------+----------------+------+-------------------------------------+----------------+---------------+
+| Method | Route | Name | Handler | Before Filters | After Filters |
++-----------+----------------+------+-------------------------------------+----------------+---------------+
+| GET(auto) | x home/foo | | \App\Controllers\Home::getFoo | | |
++-----------+----------------+------+-------------------------------------+----------------+---------------+
+```
+
+The above example shows that you have the `\App\Controllers\Home::getFoo()` method, but it is not routed because it is the default controller (`Home` by default) and the default controller name must be omitted in the URI. You should delete the `getFoo()` method.
+
+!!! note "Note"
+ Prior to v4.3.4, the invalid route was displayed as a normal route due to a bug.
+
+#### Auto Routing (Legacy)
+
+When you use Auto Routing (Legacy), the output is like the following:
+
+```none
++--------+--------------------+---------------+-----------------------------------+----------------+---------------+
+| Method | Route | Name | Handler | Before Filters | After Filters |
++--------+--------------------+---------------+-----------------------------------+----------------+---------------+
+| auto | product/list[/...] | | \App\Controllers\Product::getList | | toolbar |
++--------+--------------------+---------------+-----------------------------------+----------------+---------------+
+```
+
+The *Method* will be `auto`.
+
+`[/...]` in the *Route* column indicates any number of segments.
+
+!!! note "Note"
+ When auto-routing is enabled and you have the route `home`, it can also be accessed by `Home`, or maybe by `hOme`, `hoMe`, `HOME`, etc., but the command will show only `home`.
+
+#### Sort by Handler
+
+!!! success "Available from version 4.3.0"
+
+You can sort the routes by *handler*:
+
+```console
+php spark routes --sort-by-handler
+```
+
+#### Specify Host
+
+!!! success "Available from version 4.4.0"
+
+You can specify the host in the request URL with the `--host` option:
+
+```console
+php spark routes --host accounts.example.com
+```
+
+## Getting Routing Information
+
+In CodeIgniter 4, understanding and managing routing information is crucial for handling HTTP requests effectively. This involves retrieving details about the active controller and method, as well as the filters applied to a specific route. Below, we explore how to access this routing information to assist in tasks such as logging, debugging, or implementing conditional logic.
+
+### Retrieving the Current Controller/Method Names
+
+In some cases, you might need to determine which controller and method have been triggered by the current HTTP request. This can be useful for logging, debugging, or conditional logic based on the active controller method.
+
+CodeIgniter 4 provides a simple way to access the current route's controller and method names using the `Router` class. Here is an example:
+
+```php
+controllerName();
+
+// Retrieve the method name being executed in the controller for the current request.
+$method = $router->methodName();
+
+echo 'Current Controller: ' . $controller . ' ';
+echo 'Current Method: ' . $method;
+```
+
+This functionality is particularly useful when you need to dynamically interact with your controller or log which method is handling a particular request.
+
+### Getting Active Filters for the Current Route
+
+[Filters](filters.md) are a powerful feature that enables you to perform operations such as authentication, logging, and security checks before or after processing HTTP requests. To access the active filters for a specific route, you can use the `CodeIgniter\Router\Router::getFilters()` method from the `Router` class.
+
+This method returns a list of filters that are currently active for the route being processed:
+
+```php
+getFilters();
+
+echo 'Active Filters for the Route: ' . implode(', ', $filters);
+```
+
+!!! note "Note"
+ The `getFilters()` method returns only the filters defined for the specific route. It does not include global filters or those specified in the **app/Config/Filters.php** file.
+
+### Getting Matched Route Options for the Current Route
+
+When we're defining routes, they may have optional parameters: `filter`, `namespace`, `hostname`, `subdomain`, `offset`, `priority`, `as`. All of them are described above. Additionally, if we use `addRedirect()` we can also expect the `redirect` key. To access the values of these parameters, we can call `Router::getMatchedRouteOptions()`. Here is an example of the returned array:
+
+```php
+getMatchedRouteOptions();
+
+echo 'Route name: ' . $options['as'];
+
+print_r($options);
+
+// Route name: api:auth
+//
+// Array
+// (
+// [filter] => api-auth
+// [namespace] => App\API\v1
+// [hostname] => example.com
+// [subdomain] => api
+// [offset] => 1
+// [priority] => 1
+// [as] => api:auth
+// )
+```
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 000000000000..8d067cd7e364
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,193 @@
+# CodeIgniter4 User Guide
+
+- [Welcome to CodeIgniter4](intro/index.md)
+ - [Server Requirements](intro/requirements.md)
+ - [Credits](intro/credits.md)
+ - [PSR Compliance](intro/psr.md)
+ - [License Agreement](license.md)
+
+## Getting Started
+
+- [Installation](installation/index.md)
+ - [Composer Installation](installation/installing_composer.md)
+ - [Manual Installation](installation/installing_manual.md)
+ - [Running Your App](installation/running.md)
+ - [Worker Mode](installation/worker_mode.md)
+ - [Troubleshooting](installation/troubleshooting.md)
+ - [Deployment](installation/deployment.md)
+ - [Change Logs](changelogs/index.md)
+ - [Upgrading From a Previous Version](installation/upgrading.md)
+ - [CodeIgniter Repositories](installation/repositories.md)
+
+## Guides
+
+- [Guides](guides/index.md)
+ - [Build Your First Application](guides/first-app/index.md)
+ - [Static Pages](guides/first-app/static_pages.md)
+ - [News Section](guides/first-app/news_section.md)
+ - [Create News Items](guides/first-app/create_news_items.md)
+ - [Conclusion](guides/first-app/conclusion.md)
+ - [Getting Started with REST APIs](guides/api/index.md)
+ - [Auto Routing & Your First Endpoint](guides/api/first-endpoint.md)
+ - [Creating the Database and Model](guides/api/database-setup.md)
+ - [Building a RESTful Controller](guides/api/controller.md)
+ - [Conclusion](guides/api/conclusion.md)
+
+## Overview & General Topics
+
+- [CodeIgniter4 Overview](concepts/index.md)
+ - [Application Structure](concepts/structure.md)
+ - [Models, Views, and Controllers](concepts/mvc.md)
+ - [Autoloading Files](concepts/autoloader.md)
+ - [Services](concepts/services.md)
+ - [Factories](concepts/factories.md)
+ - [Working with HTTP Requests](concepts/http.md)
+ - [Security Guidelines](concepts/security.md)
+ - [Design and Architectural Goals](concepts/goals.md)
+
+- [General Topics](general/index.md)
+ - [Configuration](general/configuration.md)
+ - [CodeIgniter URLs](general/urls.md)
+ - [Helper Functions](general/helpers.md)
+ - [Global Functions and Constants](general/common_functions.md)
+ - [Logging Information](general/logging.md)
+ - [Error Handling](general/errors.md)
+ - [Web Page Caching](general/caching.md)
+ - [AJAX Requests](general/ajax.md)
+ - [Code Modules](general/modules.md)
+ - [Managing your Applications](general/managing_apps.md)
+ - [Handling Multiple Environments](general/environments.md)
+
+## Request Handling
+
+- [Controllers and Routing](incoming/index.md)
+ - [URI Routing](incoming/routing.md)
+ - [Controllers](incoming/controllers.md)
+ - [Controller Filters](incoming/filters.md)
+ - [Controller Attributes](incoming/controller_attributes.md)
+ - [Auto Routing (Improved)](incoming/auto_routing_improved.md)
+ - [HTTP Messages](incoming/message.md)
+ - [Request Class](incoming/request.md)
+ - [IncomingRequest Class](incoming/incomingrequest.md)
+ - [Content Negotiation](incoming/content_negotiation.md)
+ - [HTTP Method Spoofing](incoming/methodspoofing.md)
+ - [RESTful Resource Handling](incoming/restful.md)
+
+- [Building Responses](outgoing/index.md)
+ - [Views](outgoing/views.md)
+ - [View Renderer](outgoing/view_renderer.md)
+ - [View Layouts](outgoing/view_layouts.md)
+ - [View Cells](outgoing/view_cells.md)
+ - [View Parser](outgoing/view_parser.md)
+ - [View Decorators](outgoing/view_decorators.md)
+ - [HTML Table Class](outgoing/table.md)
+ - [HTTP Responses](outgoing/response.md)
+ - [API Responses](outgoing/api_responses.md)
+ - [API Resources](outgoing/api_transformers.md)
+ - [Content Security Policy](outgoing/csp.md)
+ - [Localization](outgoing/localization.md)
+ - [Alternate PHP Syntax for View Files](outgoing/alternative_php.md)
+
+## Handling Databases
+
+- [Working with Databases](database/index.md)
+ - [Quick Start: Usage Examples](database/examples.md)
+ - [Database Configuration](database/configuration.md)
+ - [Connecting to a Database](database/connecting.md)
+ - [Running Queries](database/queries.md)
+ - [Generating Query Results](database/results.md)
+ - [Query Helper Methods](database/helpers.md)
+ - [Query Builder Class](database/query_builder.md)
+ - [Transactions](database/transactions.md)
+ - [Getting Metadata](database/metadata.md)
+ - [Custom Function Calls](database/call_function.md)
+ - [Database Events](database/events.md)
+ - [Database Utilities](database/utilities.md)
+
+- [Modeling Data](models/index.md)
+ - [Using CodeIgniter's Model](models/model.md)
+ - [Using Entity Classes](models/entities.md)
+
+- [Managing Databases](dbmgmt/index.md)
+ - [Database Forge](dbmgmt/forge.md)
+ - [Database Migrations](dbmgmt/migration.md)
+ - [Database Seeding](dbmgmt/seeds.md)
+ - [Database Commands](dbmgmt/db_commands.md)
+
+## Libraries & Helpers
+
+- [Library Reference](libraries/index.md)
+ - [Caching Driver](libraries/caching.md)
+ - [Cookies](libraries/cookies.md)
+ - [Cross-Origin Resource Sharing (CORS)](libraries/cors.md)
+ - [CURLRequest Class](libraries/curlrequest.md)
+ - [Email Class](libraries/email.md)
+ - [Encryption Service](libraries/encryption.md)
+ - [Working with Files](libraries/files.md)
+ - [File Collections](libraries/file_collections.md)
+ - [Honeypot Class](libraries/honeypot.md)
+ - [Image Manipulation Class](libraries/images.md)
+ - [Pagination](libraries/pagination.md)
+ - [Publisher](libraries/publisher.md)
+ - [Security](libraries/security.md)
+ - [Session Library](libraries/sessions.md)
+ - [Throttler](libraries/throttler.md)
+ - [Times and Dates](libraries/time.md)
+ - [Typography](libraries/typography.md)
+ - [Working with Uploaded Files](libraries/uploaded_files.md)
+ - [Working with URIs](libraries/uri.md)
+ - [User Agent Class](libraries/user_agent.md)
+ - [Validation](libraries/validation.md)
+
+- [Helpers](helpers/index.md)
+ - [Array Helper](helpers/array_helper.md)
+ - [Cookie Helper](helpers/cookie_helper.md)
+ - [Date Helper](helpers/date_helper.md)
+ - [Filesystem Helper](helpers/filesystem_helper.md)
+ - [Form Helper](helpers/form_helper.md)
+ - [HTML Helper](helpers/html_helper.md)
+ - [Inflector Helper](helpers/inflector_helper.md)
+ - [Number Helper](helpers/number_helper.md)
+ - [Security Helper](helpers/security_helper.md)
+ - [Test Helper](helpers/test_helper.md)
+ - [Text Helper](helpers/text_helper.md)
+ - [URL Helper](helpers/url_helper.md)
+ - [XML Helper](helpers/xml_helper.md)
+
+## Advanced Topics
+
+- [Testing](testing/index.md)
+ - [Getting Started](testing/overview.md)
+ - [Database](testing/database.md)
+ - [Generating Data](testing/fabricator.md)
+ - [Controller Testing](testing/controllers.md)
+ - [HTTP Testing](testing/feature.md)
+ - [Testing Responses](testing/response.md)
+ - [Testing CLI Commands](testing/cli.md)
+ - [Mocking](testing/mocking.md)
+ - [Benchmarking](testing/benchmark.md)
+ - [Debugging Your Application](testing/debugging.md)
+ - [Session Testing](testing/session_testing.md)
+
+- [Command Line Usage](cli/index.md)
+ - [CLI Overview](cli/cli_overview.md)
+ - [Running Controllers via CLI](cli/cli_controllers.md)
+ - [Spark Commands](cli/spark_commands.md)
+ - [Creating Spark Commands](cli/cli_commands.md)
+ - [CLI Generators](cli/cli_generators.md)
+ - [CLI Library](cli/cli_library.md)
+ - [CLI Signals](cli/cli_signals.md)
+ - [CLIRequest Class](cli/cli_request.md)
+
+- [Extending CodeIgniter](extending/index.md)
+ - [Creating Core System Classes](extending/core_classes.md)
+ - [Replacing Common Functions](extending/common.md)
+ - [Events](extending/events.md)
+ - [Extending the Controller](extending/basecontroller.md)
+ - [Authentication](extending/authentication.md)
+ - [Creating Composer Packages](extending/composer_packages.md)
+ - [Contributing to CodeIgniter](extending/contributing.md)
+
+## Official Packages
+
+- [Official Packages](libraries/official_packages.md)
diff --git a/docs/installation/backward_compatibility_notes.md b/docs/installation/backward_compatibility_notes.md
new file mode 100644
index 000000000000..a38b3399e750
--- /dev/null
+++ b/docs/installation/backward_compatibility_notes.md
@@ -0,0 +1,13 @@
+# Backward Compatibility Notes
+
+We try to develop our products to be as backward compatible (BC) as possible.
+
+Only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.2, 4.3 etc.) may introduce new features, but must do so without breaking the existing API.
+
+However, the code is not mature and bug fixes may break compatibility in minor releases, or even in patch releases (such as 4.2.5). In that case, all the breaking changes are described in the [Change Logs](../changelogs/index.md).
+
+## What are not Breaking Changes
+
+- Deprecated items are not covered by the backward compatibility (BC) promise. They may be removed in the second subsequent **minor** version or later. For example, if an item has been deprecated since 4.3.x, it may be removed in 4.5.0.
+- System messages defined in **system/Language/en/** are strictly for internal framework use and are not covered by the backward compatibility (BC) promise. If developers are relying on language string output, they should be checking it against the function call (`lang('...')`), not the content.
+- [Named arguments](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments) are not covered by the backward compatibility (BC) promise. We may choose to rename method/function parameter names when necessary in order to improve the codebase.
diff --git a/docs/installation/deployment.md b/docs/installation/deployment.md
new file mode 100644
index 000000000000..ba91ec36a118
--- /dev/null
+++ b/docs/installation/deployment.md
@@ -0,0 +1,190 @@
+# Deployment
+
+## Optimization
+
+Before deploying your CodeIgniter application to production, there are several things you can do to make your application run more efficiently.
+
+This section describes the optimization features that CodeIgniter provides.
+
+### spark optimize
+
+!!! success "Available from version 4.5.0"
+
+The `spark optimize` command performs the following optimizations:
+
+- [Removing Dev Packages](#removing-dev-packages)
+- Enabling [Config Caching](#config-caching)
+- Enabling [FileLocator Caching](#filelocator-caching)
+
+!!! warning "Warning"
+ Do not use `spark optimize` when running the app in the [Worker Mode](worker_mode.md).
+
+### Composer Optimization
+
+#### Removing Dev Packages
+
+When you deploy, don't forget to run the following command:
+
+```console
+composer install --no-dev
+```
+
+The above command will remove the development-only Composer packages that are not needed in the production environment. This will greatly reduce the vendor folder size.
+
+#### Specifying Packages to Discover
+
+If Composer Package Auto-Discovery is enabled, all Composer packages are scanned when needed. But there is no need to scan packages that are not CodeIgniter packages, so specifying the packages to be scanned prevents unnecessary scanning.
+
+See [Specify Composer Packages](../general/modules.md#specify-composer-packages).
+
+### Config Caching
+
+!!! important "Important"
+ Once cached, configuration values are never changed until the cache is deleted, even if the configuration file or **.env** is changed.
+
+Caching the Config objects can improve performance. However, the cache must be manually deleted when changing Config values.
+
+See [Config Caching](../concepts/factories.md#config-caching).
+
+### FileLocator Caching
+
+Caching the file paths that FileLocator found can improve performance. However, the cache must be manually deleted when adding/deleting/changing file paths.
+
+See [FileLocator Caching](../concepts/autoloader.md#filelocator-caching).
+
+### PHP Preloading
+
+With PHP preloading, you can instruct the server to load essential files like functions and classes into memory during startup. This means these elements are readily available for all requests, skipping the usual loading process and boosting your application's performance. However, this comes at the cost of increased memory usage and requires restarting the PHP engine for changes to take effect.
+
+!!! note "Note"
+ If you want to use [Preloading](https://www.php.net/manual/en/opcache.preloading.php), we provide a [preload script](https://github.com/codeigniter4/CodeIgniter4/blob/develop/preload.php).
+
+#### Requirement
+
+Using preloading requires one dedicated PHP handler. Normally, web servers are configured to use one PHP handler, so one app requires a dedicated web server. If you want to use preloading for multiple apps on one web server, configure your server to use virtual hosts with multiple PHP handlers like multiple PHP-FPMs, with each virtual host using one PHP handler. Preloading keeps the relevant definitions in memory by reading the files specified in `opcache.preload`.
+
+!!! note "Note"
+ See [Running Multiple Applications with one CodeIgniter Installation](../general/managing_apps.md#running-multiple-applications-with-one-codeigniter-installation) to make one core CodeIgniter4 handle your multiple apps.
+
+#### Configuration
+
+Open `php.ini`, or `xx-opcache.ini` if you have split INI configuration in PHP. We recommend setting `opcache.preload=/path/to/preload.php` and `opcache.preload_user=myuser`.
+
+!!! note "Note"
+ `myuser` is the user running your web server. If you want to find the location of the split INI configuration, just run `php --ini` or open a `phpinfo()` page and search for *Additional .ini files parsed*.
+
+Make sure you use the appstarter installation. If using manual installation, you must change the directory in the `include` path.
+
+```php
+ __DIR__ . '/system', // <== change this line to where CI is installed
+ // ...
+ ],
+ ];
+
+ // ...
+}
+
+// ...
+```
+
+## Deployment to Shared Hosting Services
+
+!!! important "Important"
+ **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 not to the project root.
+
+### Specifying the Document Root
+
+The best way is to set the document root to the **public** folder in the server configuration:
+
+```
+└── example.com/ (project folder)
+ └── public/ (document root)
+```
+
+Check with your hosting service provider to see if you can change the document root. Unfortunately, if you cannot change the document root, go to the next way.
+
+### Using Two Directories
+
+The second way is to use two directories, and adjust the path. One is for the application and the other is the default document root.
+
+Upload the contents of the **public** folder to **public_html** (the default document root) and the other files to the directory for the application:
+
+```
+├── example.com/ (for the application)
+│ ├── app/
+│ ├── vendor/ (or system/)
+│ └── writable/
+└── public_html/ (the default document root)
+ ├── .htaccess
+ ├── favicon.ico
+ ├── index.php
+ └── robots.txt
+```
+
+See [Install CodeIgniter 4 on Shared Hosting (cPanel)](https://forum.codeigniter.com/showthread.php?tid=76779) for details.
+
+### Adding .htaccess
+
+The last resort is to add **.htaccess** to the project root.
+
+It is not recommended that you place the project folder in the document root. However, if you have no other choice, you can use this.
+
+Place your project folder as follows, where **public_html** is the document root, and create the **.htaccess** file:
+
+```
+└── public_html/ (the default document root)
+ └── example.com/ (project folder)
+ ├── .htaccess
+ └── public/
+```
+
+And edit **.htaccess** as follows:
+
+```apache
+
+ RewriteEngine On
+ RewriteRule ^(.*)$ public/$1 [L]
+
+
+
+ Require all denied
+ Satisfy All
+
+```
+
+And remove the redirect settings in **public/.htaccess**:
+
+```diff
+--- a/public/.htaccess
++++ b/public/.htaccess
+@@ -16,16 +16,6 @@ Options -Indexes
+ # http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
+ # RewriteBase /
+
+- # Redirect Trailing Slashes...
+- RewriteCond %{REQUEST_FILENAME} !-d
+- RewriteCond %{REQUEST_URI} (.+)/$
+- RewriteRule ^ %1 [L,R=301]
+-
+- # Rewrite "www.example.com -> example.com"
+- RewriteCond %{HTTPS} !=on
+- RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
+- RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
+-
+ # Checks to see if the user is attempting to access a valid file,
+ # such as an image or css document, if this isn't true it sends the
+ # request to the front controller, index.php
+```
diff --git a/docs/installation/index.md b/docs/installation/index.md
new file mode 100644
index 000000000000..4025c94ec505
--- /dev/null
+++ b/docs/installation/index.md
@@ -0,0 +1,185 @@
+# Installation
+
+CodeIgniter has two supported installation methods: manual download, or using [Composer](https://getcomposer.org). Which is right for you?
+
+- We recommend the Composer installation because it keeps CodeIgniter up to date easily.
+- If you would like the simple "download & go" install that CodeIgniter 3 is known for, choose the manual installation.
+
+However you choose to install and run CodeIgniter4, the latest [user guide](https://codeigniter.com/user_guide/) is accessible online. If you want to see previous versions, you can download them from the [codeigniter4/userguide](https://github.com/codeigniter4/userguide/releases) repository.
+
+!!! note "Note"
+ Before using CodeIgniter 4, make sure that your server meets the [requirements](../intro/requirements.md), in particular the PHP version and the PHP extensions that are needed. You may find that you have to uncomment the `php.ini` "extension" lines to enable "curl" and "intl", for instance.
+
+- [Composer Installation](installing_composer.md)
+- [Manual Installation](installing_manual.md)
+- [Running Your App](running.md)
+- [Worker Mode](worker_mode.md)
+- [Troubleshooting](troubleshooting.md)
+- [Deployment](deployment.md)
+- [Change Logs](../changelogs/index.md)
+ - [Version 4.7.7](../changelogs/v4.7.7.md)
+ - [Version 4.7.6](../changelogs/v4.7.6.md)
+ - [Version 4.7.5](../changelogs/v4.7.5.md)
+ - [Version 4.7.4](../changelogs/v4.7.4.md)
+ - [Version 4.7.3](../changelogs/v4.7.3.md)
+ - [Version 4.7.2](../changelogs/v4.7.2.md)
+ - [Version 4.7.1](../changelogs/v4.7.1.md)
+ - [Version 4.7.0](../changelogs/v4.7.0.md)
+ - [Version 4.6.5](../changelogs/v4.6.5.md)
+ - [Version 4.6.4](../changelogs/v4.6.4.md)
+ - [Version 4.6.3](../changelogs/v4.6.3.md)
+ - [Version 4.6.2](../changelogs/v4.6.2.md)
+ - [Version 4.6.1](../changelogs/v4.6.1.md)
+ - [Version 4.6.0](../changelogs/v4.6.0.md)
+ - [Version 4.5.8](../changelogs/v4.5.8.md)
+ - [Version 4.5.7](../changelogs/v4.5.7.md)
+ - [Version 4.5.6](../changelogs/v4.5.6.md)
+ - [Version 4.5.5](../changelogs/v4.5.5.md)
+ - [Version 4.5.4](../changelogs/v4.5.4.md)
+ - [Version 4.5.3](../changelogs/v4.5.3.md)
+ - [Version 4.5.2](../changelogs/v4.5.2.md)
+ - [Version 4.5.1](../changelogs/v4.5.1.md)
+ - [Version 4.5.0](../changelogs/v4.5.0.md)
+ - [Version 4.4.8](../changelogs/v4.4.8.md)
+ - [Version 4.4.7](../changelogs/v4.4.7.md)
+ - [Version 4.4.6](../changelogs/v4.4.6.md)
+ - [Version 4.4.5](../changelogs/v4.4.5.md)
+ - [Version 4.4.4](../changelogs/v4.4.4.md)
+ - [Version 4.4.3](../changelogs/v4.4.3.md)
+ - [Version 4.4.2](../changelogs/v4.4.2.md)
+ - [Version 4.4.1](../changelogs/v4.4.1.md)
+ - [Version 4.4.0](../changelogs/v4.4.0.md)
+ - [Version 4.3.8](../changelogs/v4.3.8.md)
+ - [Version 4.3.7](../changelogs/v4.3.7.md)
+ - [Version 4.3.6](../changelogs/v4.3.6.md)
+ - [Version 4.3.5](../changelogs/v4.3.5.md)
+ - [Version 4.3.4](../changelogs/v4.3.4.md)
+ - [Version 4.3.3](../changelogs/v4.3.3.md)
+ - [Version 4.3.2](../changelogs/v4.3.2.md)
+ - [Version 4.3.1](../changelogs/v4.3.1.md)
+ - [Version 4.3.0](../changelogs/v4.3.0.md)
+ - [Version 4.2.12](../changelogs/v4.2.12.md)
+ - [Version 4.2.11](../changelogs/v4.2.11.md)
+ - [Version 4.2.10](../changelogs/v4.2.10.md)
+ - [Version 4.2.9](../changelogs/v4.2.9.md)
+ - [Version 4.2.8](../changelogs/v4.2.8.md)
+ - [Version 4.2.7](../changelogs/v4.2.7.md)
+ - [Version 4.2.6](../changelogs/v4.2.6.md)
+ - [Version 4.2.5](../changelogs/v4.2.5.md)
+ - [Version 4.2.4](../changelogs/v4.2.4.md)
+ - [Version 4.2.3](../changelogs/v4.2.3.md)
+ - [Version 4.2.2](../changelogs/v4.2.2.md)
+ - [Version 4.2.1](../changelogs/v4.2.1.md)
+ - [Version 4.2.0](../changelogs/v4.2.0.md)
+ - [Version 4.1.9](../changelogs/v4.1.9.md)
+ - [Version 4.1.8](../changelogs/v4.1.8.md)
+ - [Version 4.1.7](../changelogs/v4.1.7.md)
+ - [Version 4.1.6](../changelogs/v4.1.6.md)
+ - [Version 4.1.5](../changelogs/v4.1.5.md)
+ - [Version 4.1.4](../changelogs/v4.1.4.md)
+ - [Version 4.1.3](../changelogs/v4.1.3.md)
+ - [Version 4.1.2](../changelogs/v4.1.2.md)
+ - [Version 4.1.1](../changelogs/v4.1.1.md)
+ - [Version 4.1.0](../changelogs/v4.1.0.md)
+ - [Version 4.0.5](../changelogs/v4.0.5.md)
+ - [Version 4.0.4](../changelogs/v4.0.4.md)
+ - [Version 4.0.3](../changelogs/v4.0.3.md)
+ - [Version 4.0.0](../changelogs/v4.0.0.md)
+ - [Version 4.0.0-rc.4](../changelogs/v4.0.0-rc.4.md)
+ - [Version 4.0.0-rc.3](../changelogs/v4.0.0-rc.3.md)
+ - [Version 4.0.0-rc.2](../changelogs/v4.0.0-rc.2.md)
+ - [Version 4.0.0-rc.1](../changelogs/v4.0.0-rc.1.md)
+ - [Version 4.0.0-beta.4](../changelogs/v4.0.0-beta.4.md)
+ - [Version 4.0.0-beta.3](../changelogs/v4.0.0-beta.3.md)
+ - [Version 4.0.0-beta.2](../changelogs/v4.0.0-beta.2.md)
+ - [Version 4.0.0-beta.1](../changelogs/v4.0.0-beta.1.md)
+ - [Version 4.0.0-alpha.5](../changelogs/v4.0.0-alpha.5.md)
+ - [Version 4.0.0-alpha.4](../changelogs/v4.0.0-alpha.4.md)
+ - [Version 4.0.0-alpha.3](../changelogs/v4.0.0-alpha.3.md)
+ - [Version 4.0.0-alpha.2](../changelogs/v4.0.0-alpha.2.md)
+ - [Version 4.0.0-alpha.1](../changelogs/v4.0.0-alpha.1.md)
+- [Upgrading From a Previous Version](upgrading.md)
+ - [Backward Compatibility Notes](backward_compatibility_notes.md)
+ - [Upgrading from 4.7.6 to 4.7.7](upgrade_477.md)
+ - [Upgrading from 4.7.5 to 4.7.6](upgrade_476.md)
+ - [Upgrading from 4.7.4 to 4.7.5](upgrade_475.md)
+ - [Upgrading from 4.7.3 to 4.7.4](upgrade_474.md)
+ - [Upgrading from 4.7.2 to 4.7.3](upgrade_473.md)
+ - [Upgrading from 4.7.1 to 4.7.2](upgrade_472.md)
+ - [Upgrading from 4.7.0 to 4.7.1](upgrade_471.md)
+ - [Upgrading from 4.6.x to 4.7.0](upgrade_470.md)
+ - [Upgrading from 4.6.4 to 4.6.5](upgrade_465.md)
+ - [Upgrading from 4.6.3 to 4.6.4](upgrade_464.md)
+ - [Upgrading from 4.6.2 to 4.6.3](upgrade_463.md)
+ - [Upgrading from 4.6.1 to 4.6.2](upgrade_462.md)
+ - [Upgrading from 4.6.0 to 4.6.1](upgrade_461.md)
+ - [Upgrading from 4.5.8 to 4.6.0](upgrade_460.md)
+ - [Upgrading from 4.5.7 to 4.5.8](upgrade_458.md)
+ - [Upgrading from 4.5.6 to 4.5.7](upgrade_457.md)
+ - [Upgrading from 4.5.5 to 4.5.6](upgrade_456.md)
+ - [Upgrading from 4.5.4 to 4.5.5](upgrade_455.md)
+ - [Upgrading from 4.5.3 to 4.5.4](upgrade_454.md)
+ - [Upgrading from 4.5.2 to 4.5.3](upgrade_453.md)
+ - [Upgrading from 4.5.1 to 4.5.2](upgrade_452.md)
+ - [Upgrading from 4.5.0 to 4.5.1](upgrade_451.md)
+ - [Upgrading from 4.4.8 to 4.5.0](upgrade_450.md)
+ - [Upgrading from 4.4.7 to 4.4.8](upgrade_448.md)
+ - [Upgrading from 4.4.6 to 4.4.7](upgrade_447.md)
+ - [Upgrading from 4.4.5 to 4.4.6](upgrade_446.md)
+ - [Upgrading from 4.4.4 to 4.4.5](upgrade_445.md)
+ - [Upgrading from 4.4.3 to 4.4.4](upgrade_444.md)
+ - [Upgrading from 4.4.2 to 4.4.3](upgrade_443.md)
+ - [Upgrading from 4.4.1 to 4.4.2](upgrade_442.md)
+ - [Upgrading from 4.4.0 to 4.4.1](upgrade_441.md)
+ - [Upgrading from 4.3.8 to 4.4.0](upgrade_440.md)
+ - [Upgrading from 4.3.7 to 4.3.8](upgrade_438.md)
+ - [Upgrading from 4.3.6 to 4.3.7](upgrade_437.md)
+ - [Upgrading from 4.3.5 to 4.3.6](upgrade_436.md)
+ - [Upgrading from 4.3.4 to 4.3.5](upgrade_435.md)
+ - [Upgrading from 4.3.3 to 4.3.4](upgrade_434.md)
+ - [Upgrading from 4.3.2 to 4.3.3](upgrade_433.md)
+ - [Upgrading from 4.3.1 to 4.3.2](upgrade_432.md)
+ - [Upgrading from 4.3.0 to 4.3.1](upgrade_431.md)
+ - [Upgrading from 4.2.12 to 4.3.0](upgrade_430.md)
+ - [Upgrading from 4.2.11 to 4.2.12](upgrade_4212.md)
+ - [Upgrading from 4.2.10 to 4.2.11](upgrade_4211.md)
+ - [Upgrading from 4.2.9 to 4.2.10](upgrade_4210.md)
+ - [Upgrading from 4.2.7 to 4.2.8](upgrade_428.md)
+ - [Upgrading from 4.2.6 to 4.2.7](upgrade_427.md)
+ - [Upgrading from 4.2.5 to 4.2.6](upgrade_426.md)
+ - [Upgrading from 4.2.3 to 4.2.5](upgrade_425.md)
+ - [Upgrading from 4.2.2 to 4.2.3](upgrade_423.md)
+ - [Upgrading from 4.2.1 to 4.2.2](upgrade_422.md)
+ - [Upgrading from 4.2.0 to 4.2.1](upgrade_421.md)
+ - [Upgrading from 4.1.9 to 4.2.0](upgrade_420.md)
+ - [Upgrading from 4.1.7 to 4.1.8](upgrade_418.md)
+ - [Upgrading from 4.1.6 to 4.1.7](upgrade_417.md)
+ - [Upgrading from 4.1.5 to 4.1.6](upgrade_416.md)
+ - [Upgrading from 4.1.4 to 4.1.5](upgrade_415.md)
+ - [Upgrading from 4.1.3 to 4.1.4](upgrade_414.md)
+ - [Upgrading from 4.1.2 to 4.1.3](upgrade_413.md)
+ - [Upgrading from 4.1.1 to 4.1.2](upgrade_412.md)
+ - [Upgrading from 4.0.5 to 4.1.0 or 4.1.1](upgrade_410.md)
+ - [Upgrading from 4.0.4 to 4.0.5](upgrade_405.md)
+ - [Upgrading from 4.0.x to 4.0.4](upgrade_404.md)
+ - [Upgrading from 3.x to 4.x](upgrade_4xx.md)
+ - [Upgrade Models](upgrade_models.md)
+ - [Upgrade Views](upgrade_views.md)
+ - [Upgrade Controllers](upgrade_controllers.md)
+ - [Upgrade Configuration](upgrade_configuration.md)
+ - [Upgrade Database](upgrade_database.md)
+ - [Upgrade Emails](upgrade_emails.md)
+ - [Upgrade Encryption](upgrade_encryption.md)
+ - [Upgrade Working with Uploaded Files](upgrade_file_upload.md)
+ - [Upgrade HTML Tables](upgrade_html_tables.md)
+ - [Upgrade Image Manipulation Class](upgrade_images.md)
+ - [Upgrade Localization](upgrade_localization.md)
+ - [Upgrade Migrations](upgrade_migrations.md)
+ - [Upgrade Output Class](upgrade_responses.md)
+ - [Upgrade Pagination](upgrade_pagination.md)
+ - [Upgrade Routing](upgrade_routing.md)
+ - [Upgrade Security](upgrade_security.md)
+ - [Upgrade Sessions](upgrade_sessions.md)
+ - [Upgrade Validations](upgrade_validations.md)
+ - [Upgrade View Parser](upgrade_view_parser.md)
+- [CodeIgniter Repositories](repositories.md)
diff --git a/docs/installation/installing_composer.md b/docs/installation/installing_composer.md
new file mode 100644
index 000000000000..82abfbcfbe8a
--- /dev/null
+++ b/docs/installation/installing_composer.md
@@ -0,0 +1,247 @@
+# Composer Installation
+
+Composer can be used in two ways to install CodeIgniter4 on your system.
+
+!!! important "Important"
+ CodeIgniter4 requires Composer 2.0.14 or later.
+
+!!! note "Note"
+ If you are not familiar with Composer, we recommend you read [Basic usage](https://getcomposer.org/doc/01-basic-usage.md) first.
+
+The first technique describes creating a skeleton project (app starter) using CodeIgniter4, which you would then use as the base for a new webapp. The second technique described below lets you add CodeIgniter4 to an existing webapp.
+
+!!! note "Note"
+ If you are using a Git repository to store your code, or for collaboration with others, then the **vendor** folder would normally be "git ignored". In such a case, you will need to do a `composer install` (or `composer update` if you want to update all Composer dependencies) when you clone the repository to a new system.
+
+## App Starter
+
+The [CodeIgniter 4 app starter](https://github.com/codeigniter4/appstarter) repository holds a skeleton application, with a composer dependency on the latest released version of the framework.
+
+This installation technique would suit a developer who wishes to start a new CodeIgniter4-based project.
+
+### Installation
+
+In the folder above your project root:
+
+```console
+composer create-project codeigniter4/appstarter project-root
+```
+
+The command above will create a **project-root** folder.
+
+If you omit the "project-root" argument, the command will create an "appstarter" folder, which can be renamed as appropriate.
+
+!!! note "Note"
+ Before v4.4.0, the CodeIgniter autoloader did not allow special characters that are illegal in filenames on certain operating systems. The symbols that can be used are `/`, `_`, `.`, `:`, `\` and space. So if you installed CodeIgniter under a folder that contains special characters like `(`, `)`, etc., CodeIgniter didn't work. Since v4.4.0, this restriction has been removed.
+
+!!! important "Important"
+ When you deploy to your production server, don't forget to run the following command:
+
+ ```console
+ composer install --no-dev
+ ```
+
+ The above command will remove the development-only Composer packages that are not needed in the production environment. This will greatly reduce the vendor folder size.
+
+#### Installing Previous Versions
+
+For example, you may want to install v4.4.8 after v4.5.0 has been released.
+
+In that case, specify the version in the command:
+
+```console
+composer create-project codeigniter4/appstarter:4.4.8 project-root
+```
+
+Then, open **composer.json** in your project root folder, and specify the framework version:
+
+```text
+"require": {
+ ...
+ "codeigniter4/framework": "4.4.8"
+},
+```
+
+Then, run the `composer update` command.
+
+!!! note "Note"
+ When you use a fixed version number like `"codeigniter4/framework": "4.4.8"` in your **composer.json**, the `composer update` command will not update the framework to the latest version. See [Writing Version Constraints](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints) for how to specify the version.
+
+### Initial Configuration
+
+After installation, a few initial configurations are required. See [Initial Configuration](running.md#initial-configuration) for the details.
+
+### Upgrading
+
+Whenever there is a new release, run this from the command line in your project root:
+
+```console
+composer update
+```
+
+Read the [upgrade instructions](upgrading.md) and [change log](../changelogs/index.md), and check Breaking Changes and Enhancements.
+
+#### Upgrading to a Specified Version
+
+For example, you may want to upgrade from v4.4.7 to v4.4.8 after v4.5.0 has been released.
+
+In that case, open **composer.json** in your project root folder, and specify the framework version:
+
+```text
+"require": {
+ ...
+ "codeigniter4/framework": "4.4.8"
+},
+```
+
+Then, run the `composer update` command.
+
+!!! note "Note"
+ When you use a fixed version number like `"codeigniter4/framework": "4.4.8"` in your **composer.json**, the `composer update` command will not update the framework to the latest version. See [Writing Version Constraints](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints) for how to specify the version.
+
+### Pros
+
+Simple installation; easy to update.
+
+### Cons
+
+You still need to check for file changes in the **project space** (root, app, public, writable) and merge them after updating.
+
+!!! note "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### Structure
+
+Folders in your project after setup:
+
+- app, public, tests, writable
+- vendor/codeigniter4/framework/system
+
+### Latest Dev
+
+The App Starter repo comes with a **builds** script to switch Composer sources between the current stable release and the latest development branch of the framework. This script is for developers who are willing to live with the latest unreleased changes, which may be unstable. This way you can help find bugs and improve new features.
+
+The [development user guide](https://codeigniter4.github.io/CodeIgniter4/) is accessible online. Note that this differs from the released user guide, and will pertain to the develop branch explicitly.
+
+Before running the script, check `$files`, which is the list of files to be changed. This is useful if you have additional places in the code that mention the directory with the framework.
+
+!!! note "Note"
+ You should not rely on the version of the framework in your project because the development code may contain an incorrect number.
+
+#### Update for Latest Dev
+
+In your project root:
+
+```console
+php builds development
+```
+
+The command above will update **composer.json** to point to the `develop` branch of the working repository, and update the corresponding paths in config and XML files.
+
+After using the `builds` command, be sure to run `composer update` to sync your vendor folder with the latest target build. Then, check the [Upgrading From a Previous Version](upgrading.md) and update project files if necessary.
+
+#### Next Minor Version
+
+If you want to use the next minor version branch:
+
+```console
+php builds next
+```
+
+And run `composer update` to sync your vendor folder with the latest target build. Then, check the Upgrading Guide (**user_guide_src/source/installation/upgrade_{version}.rst**) and update project files if necessary.
+
+#### Revert to Stable Release
+
+To revert the changes, run:
+
+```console
+php builds release
+```
+
+## Adding CodeIgniter4 to an Existing Project
+
+The same [CodeIgniter 4 framework](https://github.com/codeigniter4/framework) repository described in "Manual Installation" can also be added to an existing project using Composer.
+
+### Installation
+
+Develop your app inside the `app` folder, and the `public` folder will be your document root.
+
+In your project root:
+
+```console
+composer require codeigniter4/framework
+```
+
+!!! important "Important"
+ When you deploy to your production server, don't forget to run the following command:
+
+ ```console
+ composer install --no-dev
+ ```
+
+ The above command will remove the development-only Composer packages that are not needed in the production environment. This will greatly reduce the vendor folder size.
+
+### Setting Up
+
+1. Copy the **app**, **public**, **tests** and **writable** folders from **vendor/codeigniter4/framework** to your project root.
+2. Copy the **env**, **phpunit.dist.xml** and **spark** files from **vendor/codeigniter4/framework** to your project root.
+3. You will have to adjust the `$systemDirectory` property in **app/Config/Paths.php** to refer to the vendor one, e.g., `__DIR__ . '/../../vendor/codeigniter4/framework/system'`.
+
+### Initial Configuration
+
+A few initial configurations are required. See [Initial Configuration](running.md#initial-configuration) for the details.
+
+### Upgrading
+
+Whenever there is a new release, run this from the command line in your project root:
+
+```console
+composer update
+```
+
+Read the [upgrade instructions](upgrading.md) and [change log](../changelogs/index.md), and check Breaking Changes and Enhancements.
+
+#### Upgrading to a Specified Version
+
+For example, you may want to upgrade from v4.4.7 to v4.4.8 after v4.5.0 has been released.
+
+In that case, open **composer.json** in your project root folder, and specify the framework version:
+
+```text
+"require": {
+ ...
+ "codeigniter4/framework": "4.4.8"
+},
+```
+
+Then, run the `composer update` command.
+
+### Pros
+
+Relatively simple installation; easy to update.
+
+### Cons
+
+You still need to check for file changes in the **project space** (root, app, public, writable) after updating.
+
+!!! note "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### Structure
+
+Folders in your project after setup:
+
+- app, public, tests, writable
+- vendor/codeigniter4/framework/system
+
+## Translations Installation
+
+If you want to take advantage of the system message translations, they can be added to your project in a similar fashion.
+
+From the command line inside your project root:
+
+```console
+composer require codeigniter4/translations
+```
+
+Copy the contents of the **vendor/codeigniter4/translations/Language** folder to your **app/Language** folder. These will be updated along with the framework whenever you do a `composer update`.
diff --git a/docs/installation/installing_manual.md b/docs/installation/installing_manual.md
new file mode 100644
index 000000000000..e765fa9eb92c
--- /dev/null
+++ b/docs/installation/installing_manual.md
@@ -0,0 +1,47 @@
+# Manual Installation
+
+The [CodeIgniter 4 framework](https://github.com/codeigniter4/framework) repository holds the released versions of the framework. It is intended for developers who do not wish to use Composer.
+
+Develop your app inside the **app** folder, and the **public** folder will be your public-facing document root. Do not change anything inside the **system** folder!
+
+!!! note "Note"
+ This is the installation technique closest to that described for [CodeIgniter 3](https://codeigniter.com/userguide3/installation/index.html).
+
+## Installation
+
+Download the [latest version](https://github.com/CodeIgniter4/framework/releases/latest), and extract it to become your project root.
+
+!!! note "Note"
+ Before v4.4.0, the CodeIgniter autoloader did not allow special characters that are illegal in filenames on certain operating systems. The symbols that can be used are `/`, `_`, `.`, `:`, `\` and space. So if you installed CodeIgniter under a folder that contains special characters like `(`, `)`, etc., CodeIgniter didn't work. Since v4.4.0, this restriction has been removed.
+
+## Initial Configuration
+
+After installation, a few initial configurations are required. See [Initial Configuration](running.md#initial-configuration) for details.
+
+## Upgrading
+
+Download a new copy of the framework, and then replace the **system** folder.
+
+Read the [upgrade instructions](upgrading.md) and [change log](../changelogs/index.md), and check Breaking Changes and Enhancements.
+
+## Pros
+
+Download and run.
+
+## Cons
+
+You need to check for file changes in the **project space** (root, app, public, tests, writable) and merge them by yourself.
+
+## Structure
+
+Folders in your project after setup:
+
+- app, public, tests, writable, system
+
+## Translations Installation
+
+If you want to take advantage of the system message translations, they can be added to your project in a similar fashion.
+
+Download the [latest version of them](https://github.com/codeigniter4/translations/releases/latest). Extract the downloaded zip, and copy the contents of its **Language** folder to your **app/Language** folder.
+
+This would need to be repeated to incorporate any updates to the translations.
diff --git a/docs/installation/repositories.md b/docs/installation/repositories.md
new file mode 100644
index 000000000000..3b141fe9e392
--- /dev/null
+++ b/docs/installation/repositories.md
@@ -0,0 +1,55 @@
+# CodeIgniter Repositories
+
+## codeigniter4 organization
+
+The CodeIgniter 4 open source project has its own [GitHub organization](https://github.com/codeigniter4).
+
+There are several development repositories of interest to potential contributors:
+
+| Repository | Audience | Description |
+|----|----|----|
+| [CodeIgniter4](https://github.com/codeigniter4/CodeIgniter4) | contributors | Project codebase, including tests & user guide sources |
+| [translations](https://github.com/codeigniter4/translations) | developers | System message translations |
+| [coding-standard](https://github.com/CodeIgniter/coding-standard) | contributors | Coding style conventions & rules |
+| [devkit](https://github.com/codeigniter4/devkit) | developers | Development toolkit for CodeIgniter libraries and projects |
+| [settings](https://github.com/codeigniter4/settings) | developers | Settings Library for CodeIgniter 4 |
+| [shield](https://codeigniter4.github.io/shield) | developers | Authentication and Authorization Library for CodeIgniter 4 |
+| [tasks](https://github.com/codeigniter4/tasks) | developers | Task Scheduler for CodeIgniter 4 |
+| [cache](https://github.com/codeigniter4/cache) | developers | PSR-6 and PSR-16 Cache Adapters for CodeIgniter 4 |
+| [queue](https://github.com/codeigniter4/queue) | developers | Job Queue for CodeIgniter 4 |
+
+There are also several deployment repositories, referenced in the installation directions. The deployment repositories are built automatically when a new version is released, and they are not directly contributed to.
+
+| Repository | Audience | Description |
+|----|----|----|
+| [framework](https://github.com/codeigniter4/framework) | developers | Released versions of the framework |
+| [appstarter](https://github.com/codeigniter4/appstarter) | developers | Starter project (app/public/writable). Dependent on "framework" |
+| [userguide](https://github.com/codeigniter4/userguide) | anyone | Pre-built user guide |
+
+In all the above, the latest version of a repository can be downloaded from the **Releases** section of its GitHub repository page. The current development version of each can be cloned or downloaded using the **Code** dropdown on the repository homepage.
+
+## Composer Packages
+
+We also maintain composer-installable packages on [packagist.org](https://packagist.org/search/?query=codeigniter4). These correspond to the repositories mentioned above:
+
+- [codeigniter4/framework](https://packagist.org/packages/codeigniter4/framework)
+- [codeigniter4/appstarter](https://packagist.org/packages/codeigniter4/appstarter)
+- [codeigniter4/translations](https://packagist.org/packages/codeigniter4/translations)
+- [codeigniter/coding-standard](https://packagist.org/packages/codeigniter/coding-standard)
+- [codeigniter4/devkit](https://packagist.org/packages/codeigniter4/devkit)
+- [codeigniter4/settings](https://packagist.org/packages/codeigniter4/settings)
+- [codeigniter4/shield](https://packagist.org/packages/codeigniter4/shield)
+- [codeigniter4/cache](https://packagist.org/packages/codeigniter4/cache)
+
+See the [Installation](index.md) page for more information.
+
+## CodeIgniter 4 Projects
+
+We maintain a [codeigniter4projects](https://github.com/codeigniter4projects) organization on GitHub as well, with projects that are not part of the framework, but which showcase it or make it easier to work with!
+
+| Repository | Audience | Description |
+|----|----|----|
+| [website](https://github.com/codeigniter4projects/website) | developers | The codeigniter.com website, written in CodeIgniter 4 |
+| [playground](https://github.com/codeigniter4projects/playground) | developers | Basic code examples in project form. Still growing. |
+
+These are not composer-installable repositories.
diff --git a/docs/installation/running.md b/docs/installation/running.md
new file mode 100644
index 000000000000..d246df841c90
--- /dev/null
+++ b/docs/installation/running.md
@@ -0,0 +1,455 @@
+# Running Your App
+
+A CodeIgniter 4 app can be run in a number of different ways: hosted on a web server, using virtualization, or using CodeIgniter's command line tool for testing. This section addresses how to use each technique, and explains some of the pros and cons of them.
+
+!!! important "Important"
+ You should always be careful about the case of filenames. Many developers develop on case-insensitive file systems on Windows or macOS. However, most server environments use case-sensitive file systems. If the file name case is incorrect, code that works locally will not work on the server.
+
+If you're new to CodeIgniter, please read the [Getting Started](../intro/index.md) section of the User Guide to begin learning how to build dynamic PHP applications. Enjoy!
+
+## Initial Configuration
+
+### Configure for Your Site URIs
+
+Open the **app/Config/App.php** file with a text editor.
+
+1. `$baseURL`
+ Set your base URL in `$baseURL`. If you need more flexibility, the baseURL may be set within the [.env](../general/configuration.md#dotenv-file) file as `app.baseURL = 'http://example.com/'`. **Always use a trailing slash on your base URL!**
+
+ !!! note "Note"
+ If you don't set the `$baseURL` correctly, in development mode, the debug toolbar may not load properly and web pages may take considerably longer to display.
+
+2. `$indexPage`
+ If you don't want to include **index.php** in your site URIs, set `$indexPage` to `''`. The setting will be used when the framework generates your site URIs.
+
+ !!! note "Note"
+ You may need to configure your web server to access your site with a URL that does not contain **index.php**. See [CodeIgniter URLs](../general/urls.md#removing-the-indexphp-file).
+
+### Configure Database Connection Settings
+
+If you intend to use a database, open the **app/Config/Database.php** file with a text editor and set your database settings. Alternatively, these could be set in your **.env** file. See [Database Configuration](../database/configuration.md#description-of-values) for details.
+
+### Set to Development Mode
+
+If the site is not on the production server, set `CI_ENVIRONMENT` to `development` in the **.env** file to take advantage of the debugging tools provided. See [Setting Development Mode](../guides/first-app/index.md#setting-development-mode) for details.
+
+!!! important "Important"
+ In production environments, you should disable error display and any other development-only functionality. In CodeIgniter, this can be done by setting the environment to "production". By default, the application will run using the "production" environment. See also [The ENVIRONMENT Constant](../general/environments.md#the-environment-constant).
+
+### Set Writable Folder Permission
+
+If you will be running your site using a web server (e.g., Apache or nginx), you will need to modify the permissions for the **writable** folder inside your project, so that it is writable by the user or account used by your web server.
+
+### Checking PHP ini Settings
+
+!!! success "Available from version 4.5.0"
+
+[PHP ini settings](https://www.php.net/manual/en/ini.list.php) change the behavior of PHP. CodeIgniter provides a command to check important PHP settings.
+
+```console
+php spark phpini:check
+```
+
+The *Recommended* column shows the recommended values for production environments. They may differ in development environments.
+
+!!! note "Note"
+ If you cannot use the spark command, you can use `CheckPhpIni::run(false)` in your controller.
+
+ E.g.,
+
+ ```php
+ .
+
+!!! note "Note"
+ The built-in development server should only be used on local development machines. It should NEVER be used on a production server.
+
+If you need to run the site on a host other than simply localhost, you'll first need to add the host to your **hosts** file. The exact location of the file varies in each of the main operating systems, though all unix-type systems (including macOS) will typically keep the file at **/etc/hosts**.
+
+The local development server can be customized with three command line options:
+
+- You can use the `--host` CLI option to specify a different host to run the application at:
+
+ ```console
+ php spark serve --host example.dev
+ ```
+
+- By default, the server runs on port 8080 but you might have more than one site running, or already have another application using that port. You can use the `--port` CLI option to specify a different one:
+
+ ```console
+ php spark serve --port 8081
+ ```
+
+- You can also choose a specific version of PHP to use, with the `--php` CLI option, with its value set to the path of the PHP executable you want to use:
+
+ ```console
+ php spark serve --php /usr/bin/php8.2
+ ```
+
+## Worker Mode with FrankenPHP
+
+!!! success "Available from version 4.7.0"
+
+!!! important "Important"
+ Worker Mode is currently **experimental**. The only officially supported worker implementation is **FrankenPHP**, which is backed by the PHP Foundation.
+
+FrankenPHP is a modern PHP application server that supports Worker Mode, allowing your application to handle multiple requests within the same PHP process for improved performance.
+
+### Quick Start
+
+1. Install FrankenPHP using [static binaries](https://github.com/php/frankenphp/releases)
+
+2. Generate worker mode files:
+
+ ```console
+ php spark worker:install
+ ```
+
+3. Start the server:
+
+ ```console
+ frankenphp run
+ ```
+
+For detailed configuration, performance tuning, and important considerations about state management, see [Worker Mode](worker_mode.md).
+
+## Hosting with Apache
+
+A CodeIgniter4 webapp is normally hosted on a web server. Apache HTTP Server is the "standard" platform, and is assumed in much of our documentation.
+
+Apache is bundled with many platforms, but can also be downloaded in a bundle with a database engine and PHP from [Bitnami](https://bitnami.com/stack/lamp).
+
+### Configure Main Config File
+
+#### Enabling mod_rewrite
+
+The "mod_rewrite" module enables URLs without "**index.php**" in them, and is assumed in our user guide.
+
+Make sure that the rewrite module is enabled (uncommented) in the main configuration file, e.g., **apache2/conf/httpd.conf**:
+
+```apache
+LoadModule rewrite_module modules/mod_rewrite.so
+```
+
+#### Setting Document Root
+
+Also make sure that the default document root's `` element enables this too, in the `AllowOverride` setting:
+
+```apache
+
+ Options Indexes FollowSymLinks
+ AllowOverride All
+ Require all granted
+
+```
+
+### Hosting with VirtualHost
+
+We recommend using "virtual hosting" to run your apps. You can set up different aliases for each of the apps you work on.
+
+#### Enabling vhost_alias_module
+
+Make sure that the virtual hosting module is enabled (uncommented) in the main configuration file, e.g., **apache2/conf/httpd.conf**:
+
+```apache
+LoadModule vhost_alias_module modules/mod_vhost_alias.so
+```
+
+#### Adding Host Alias
+
+Add a host alias in your "hosts" file, typically **/etc/hosts** on unix-type platforms, or **c:\Windows\System32\drivers\etc\hosts** on Windows.
+
+Add a line to the file. This could be `myproject.local` or `myproject.test`, for instance:
+
+```
+127.0.0.1 myproject.local
+```
+
+#### Setting VirtualHost
+
+Add a `` element for your webapp inside the virtual hosting configuration, e.g., **apache2/conf/extra/httpd-vhost.conf**:
+
+```apache
+
+ DocumentRoot "/opt/lamp/apache2/myproject/public"
+ ServerName myproject.local
+ ErrorLog "logs/myproject-error_log"
+ CustomLog "logs/myproject-access_log" common
+
+
+ AllowOverride All
+ Require all granted
+
+
+```
+
+The above configuration assumes the project folder is located as follows:
+
+```text
+apache2/
+ ├── myproject/ (Project Folder)
+ │ └── public/ (DocumentRoot for myproject.local)
+ └── htdocs/
+```
+
+Restart Apache.
+
+#### Testing
+
+With the above configuration, your webapp would be accessed with the URL **http://myproject.local/** in your browser.
+
+Apache needs to be restarted whenever you change its configuration.
+
+### Hosting with Subfolder
+
+If you want a baseURL like **http://localhost/myproject/** with a subfolder, there are three ways.
+
+#### Making Symlink
+
+Place your project folder as follows, where **htdocs** is the Apache document root:
+
+```
+├── myproject/ (project folder)
+│ └── public/
+└── htdocs/
+```
+
+Navigate to the **htdocs** folder and create a symbolic link as follows:
+
+```console
+cd htdocs/
+ln -s ../myproject/public/ myproject
+```
+
+#### Using Alias
+
+Place your project folder as follows, where **htdocs** is the Apache document root:
+
+```
+├── myproject/ (project folder)
+│ └── public/
+└── htdocs/
+```
+
+Add the following in the main configuration file, e.g., **apache2/conf/httpd.conf**:
+
+```apache
+Alias /myproject /opt/lamp/apache2/myproject/public
+
+ AllowOverride All
+ Require all granted
+
+```
+
+Restart Apache.
+
+#### Adding .htaccess
+
+The last resort is to add **.htaccess** to the project root.
+
+It is not recommended that you place the project folder in the document root. However, if you have no other choice, like on a shared server, you can use this.
+
+Place your project folder as follows, where **htdocs** is the Apache document root, and create the **.htaccess** file:
+
+```
+└── htdocs/
+ └── myproject/ (project folder)
+ ├── .htaccess
+ └── public/
+```
+
+And edit **.htaccess** as follows:
+
+```apache
+
+ RewriteEngine On
+ RewriteRule ^(.*)$ public/$1 [L]
+
+
+
+ Require all denied
+ Satisfy All
+
+```
+
+And remove the redirect settings in **public/.htaccess**:
+
+```diff
+--- a/public/.htaccess
++++ b/public/.htaccess
+@@ -16,16 +16,6 @@ Options -Indexes
+ # http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
+ # RewriteBase /
+
+- # Redirect Trailing Slashes...
+- RewriteCond %{REQUEST_FILENAME} !-d
+- RewriteCond %{REQUEST_URI} (.+)/$
+- RewriteRule ^ %1 [L,R=301]
+-
+- # Rewrite "www.example.com -> example.com"
+- RewriteCond %{HTTPS} !=on
+- RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
+- RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
+-
+ # Checks to see if the user is attempting to access a valid file,
+ # such as an image or css document, if this isn't true it sends the
+ # request to the front controller, index.php
+```
+
+### Hosting with mod_userdir (Shared Hosts)
+
+A common practice in shared hosting environments is to use the Apache module "mod_userdir" to enable per-user Virtual Hosts automatically. Additional configuration is required to allow CodeIgniter4 to be run from these per-user directories.
+
+The following assumes that the server is already configured for mod_userdir. A guide to enabling this module is available [in the Apache documentation](https://httpd.apache.org/docs/2.4/howto/public_html.html).
+
+Because CodeIgniter4 expects the server to find the framework front controller at **public/index.php** by default, you must specify this location as an alternative to search for the request (even if CodeIgniter4 is installed within the per-user web directory).
+
+The default user web directory **~/public_html** is specified by the `UserDir` directive, typically in **apache2/mods-available/userdir.conf** or **apache2/conf/extra/httpd-userdir.conf**:
+
+```apache
+UserDir public_html
+```
+
+So you will need to configure Apache to look for CodeIgniter's public directory first before trying to serve the default:
+
+```apache
+UserDir "public_html/public" "public_html"
+```
+
+Be sure to specify options and permissions for the CodeIgniter public directory as well. A **userdir.conf** might look like:
+
+```apache
+
+ UserDir "public_html/public" "public_html"
+ UserDir disabled root
+
+
+ AllowOverride All
+ Options MultiViews Indexes FollowSymLinks
+
+ # Apache <= 2.2:
+ # Order allow,deny
+ # Allow from all
+
+ # Apache >= 2.4:
+ Require all granted
+
+
+ # Apache <= 2.2:
+ # Order deny,allow
+ # Deny from all
+
+ # Apache >= 2.4:
+ Require all denied
+
+
+
+
+ AllowOverride All
+ Options MultiViews Indexes FollowSymLinks
+
+ # Apache <= 2.2:
+ # Order allow,deny
+ # Allow from all
+
+ # Apache >= 2.4:
+ Require all granted
+
+
+ # Apache <= 2.2:
+ # Order deny,allow
+ # Deny from all
+
+ # Apache >= 2.4:
+ Require all denied
+
+
+
+```
+
+### Removing the index.php
+
+See [CodeIgniter URLs](../general/urls.md#apache-web-server).
+
+### Setting Environment
+
+See [Handling Multiple Environments](../general/environments.md#apache).
+
+## Hosting with nginx
+
+nginx is the second most widely used HTTP server for web hosting. Here you can find an example configuration using PHP 8.2 FPM (unix sockets) under Ubuntu Server.
+
+### default.conf
+
+This configuration enables URLs without "**index.php**" in them and uses CodeIgniter's "404 - File Not Found" for URLs ending with ".php".
+
+```nginx
+server {
+ listen 80;
+ listen [::]:80;
+
+ server_name example.com;
+
+ root /var/www/example.com/public;
+ index index.php index.html index.htm;
+
+ location / {
+ try_files $uri $uri/ /index.php$is_args$args;
+ }
+
+ location ~ \.php$ {
+ include snippets/fastcgi-php.conf;
+
+ # With php-fpm:
+ fastcgi_pass unix:/run/php/php8.2-fpm.sock;
+ # With php-cgi:
+ # fastcgi_pass 127.0.0.1:9000;
+ }
+
+ error_page 404 /index.php;
+
+ # deny access to hidden files such as .htaccess
+ location ~ /\. {
+ deny all;
+ }
+}
+```
+
+### Setting Environment
+
+See [Handling Multiple Environments](../general/environments.md#nginx).
+
+## Deployment to Shared Hosting Services
+
+See [Deployment](deployment.md#deployment-to-shared-hosting-services).
+
+## Bootstrapping the App
+
+In some scenarios, you will want to load the framework without actually running the whole application. This is particularly useful for unit testing your project, but may also be handy for using third-party tools to analyze and modify your code. The framework comes with two separate bootstrap scripts specifically for these scenarios:
+
+- **system/Test/bootstrap.php**: This script is used primarily for unit testing.
+- **system/util_bootstrap.php**: This script is used for other scripts that need access to the framework. It is recommended for use in scripts that are not part of testing, as this will NOT fail gracefully if an exception is thrown.
+
+Most of the paths to your project are defined during the bootstrap process. You may use pre-defined constants to override these, but when using the defaults be sure that your paths align with the expected directory structure for your installation method.
diff --git a/docs/installation/troubleshooting.md b/docs/installation/troubleshooting.md
new file mode 100644
index 000000000000..e1e149f9fb9f
--- /dev/null
+++ b/docs/installation/troubleshooting.md
@@ -0,0 +1,97 @@
+# Troubleshooting
+
+Here are some common installation problems, and suggested workarounds.
+
+## How do I know if my install is working?
+
+From the command line, at your project root:
+
+```console
+php spark serve
+```
+
+`http://localhost:8080` in your browser should then show the default welcome page:
+
+
+
+## I have to include index.php in my URL
+
+If a URL like `/mypage/find/apple` doesn't work, but the similar URL `/index.php/mypage/find/apple` does, that sounds like your **.htaccess** rules (for Apache) are not set up properly, or the `mod_rewrite` extension in Apache's **httpd.conf** is commented out. See [Removing the index.php file](../general/urls.md#removing-the-indexphp-file).
+
+## Only the default page loads
+
+If you find that no matter what you put in your URL, only your default page is loading, it might be that your server does not support the REQUEST_URI variable needed to serve search-engine friendly URLs. As a first step, open your **app/Config/App.php** file and look for the URI Protocol information. It will recommend that you try a couple of alternate settings. If it still doesn't work after you've tried this, you'll need to force CodeIgniter to add a question mark (`?`) to your URLs. To do this, open your **app/Config/App.php** file and change this:
+
+```php
+proxyIPs` directly
+- `Request::__construct()`: The constructor will no longer take `Config\App` and has been made nullable to aid transition
+- `Response[Interface]::getReason()`: Use `getReasonPhrase()` instead
+- `Response[Interface]::getStatusCode()`: The explicit `int` return type will be removed (no action required)
+
+### ResponseInterface
+
+This interface intends to include the necessary methods for any framework-compatible response class. A number of methods expected by the framework were missing and have now been added. If you use any classes that implement `ResponseInterface` directly, they will need to be compatible with the updated requirements. These methods are as follows:
+
+- `setLastModified($date)`
+- `setLink(PagerInterface $pager)`
+- `setJSON($body, bool $unencoded = false)`
+- `getJSON()`
+- `setXML($body)`
+- `getXML()`
+- `send()`
+- `sendHeaders()`
+- `sendBody()`
+- `setCookie($name, $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = false, $httponly = false, $samesite = null)`
+- `hasCookie(string $name, string $value = null, string $prefix = ''): bool`
+- `getCookie(string $name = null, string $prefix = '')`
+- `deleteCookie(string $name = '', string $domain = '', string $path = '/', string $prefix = '')`
+- `getCookies()`
+- `redirect(string $uri, string $method = 'auto', int $code = null)`
+- `download(string $filename = '', $data = '', bool $setMime = false)`
+
+To facilitate use of this interface, these methods have been moved from the framework's `Response` into a `ResponseTrait` which you may use, and `DownloadResponse` now extends `Response` directly to ensure maximum compatibility.
+
+### Config\Services
+
+Service discovery has been updated to allow third-party services (when enabled via Modules) to take precedence over core services. Update **app/Config/Services.php** so the class extends `CodeIgniter\Config\BaseService` to allow proper discovery of third-party services.
+
+## Project Files
+
+Numerous files in the project space (root, app, public, writable) received updates. Due to these files being outside of the system scope, they will not be changed without your intervention. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+!!! note "Note"
+ Except in very rare cases for bug fixes, no changes made to files for the project space will break your application. All changes noted here are optional until the next major version, and any mandatory changes will be covered in the sections above.
+
+### 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:
+
+- `app/Views/*`
+- `public/index.php`
+- `public/.htaccess`
+- `spark`
+- `phpunit.xml.dist`
+- `composer.json`
+
+### 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:
+
+- `LICENSE`
+- `README.md`
+- `app/Config/App.php`
+- `app/Config/Autoload.php`
+- `app/Config/Boot/development.php`
+- `app/Config/Boot/production.php`
+- `app/Config/Boot/testing.php`
+- `app/Config/Cache.php`
+- `app/Config/Constants.php`
+- `app/Config/ContentSecurityPolicy.php`
+- `app/Config/Database.php`
+- `app/Config/DocTypes.php`
+- `app/Config/Email.php`
+- `app/Config/Encryption.php`
+- `app/Config/Events.php`
+- `app/Config/Exceptions.php`
+- `app/Config/Filters.php`
+- `app/Config/ForeignCharacters.php`
+- `app/Config/Format.php`
+- `app/Config/Generators.php`
+- `app/Config/Honeypot.php`
+- `app/Config/Images.php`
+- `app/Config/Kint.php`
+- `app/Config/Logger.php`
+- `app/Config/Migrations.php`
+- `app/Config/Mimes.php`
+- `app/Config/Modules.php`
+- `app/Config/Pager.php`
+- `app/Config/Paths.php`
+- `app/Config/Routes.php`
+- `app/Config/Security.php`
+- `app/Config/Services.php`
+- `app/Config/Toolbar.php`
+- `app/Config/UserAgents.php`
+- `app/Config/Validation.php`
+- `app/Config/View.php`
+- `app/Controllers/BaseController.php`
+- `app/Controllers/Home.php`
+- `app/Views/errors/cli/error_404.php`
+- `app/Views/errors/cli/error_exception.php`
+- `app/Views/errors/html/debug.css`
+- `app/Views/errors/html/debug.js`
+- `app/Views/errors/html/error_exception.php`
+- `composer.json`
+- `env`
+- `license.txt`
+- `phpunit.xml.dist`
+- `public/.htaccess`
+- `public/index.php`
+- `spark`
diff --git a/docs/installation/upgrade_410.md b/docs/installation/upgrade_410.md
new file mode 100644
index 000000000000..ffa3535d0418
--- /dev/null
+++ b/docs/installation/upgrade_410.md
@@ -0,0 +1,13 @@
+# Upgrading from 4.0.5 to 4.1.0 or 4.1.1
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Legacy Autoloading
+
+The `Autoloader::loadLegacy()` method was originally for the transition to CodeIgniter v4. Since v4.1.0, this support was removed. All classes must be namespaced.
diff --git a/docs/installation/upgrade_412.md b/docs/installation/upgrade_412.md
new file mode 100644
index 000000000000..a02678e7b8ca
--- /dev/null
+++ b/docs/installation/upgrade_412.md
@@ -0,0 +1,124 @@
+# Upgrading from 4.1.1 to 4.1.2
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### current_url() and indexPage
+
+Due to [a bug](https://github.com/codeigniter4/CodeIgniter4/issues/4116) in `current_url()`, the resulting URIs could be incorrect for a project's configuration, most importantly: `indexPage` would *not* be included. Projects using `App::$indexPage` should expect altered values from `current_url()` and all its dependencies (including Response Testing, Pager, Form Helper, and View Parser). Update your projects accordingly.
+
+### Cache Keys
+
+Cache handlers had wildly different compatibility for keys. The updated cache drivers now pass all keys through validation, roughly matching PSR-6's recommendations:
+
+A string of at least one character that uniquely identifies a cached item. Implementing libraries MUST support keys consisting of the characters A-Z, a-z, 0-9, \_, and . in any order in UTF-8 encoding and a length of up to 64 characters. Implementing libraries MAY support additional characters and encodings or longer lengths, but must support at least that minimum. Libraries are responsible for their own escaping of key strings as appropriate, but MUST be able to return the original unmodified key string. The following characters are reserved for future extensions and MUST NOT be supported by implementing libraries: `{}()/\@:`
+
+Update your projects to remove any invalid cache keys.
+
+### BaseConnection::query() Return Values
+
+The `BaseConnection::query()` method in prior versions was incorrectly returning BaseResult objects even if the query failed. This method will now return `false` for failed queries (or throw an Exception if `DBDebug` is `true`) and will return booleans for write-type queries. Review any use of the `query()` method and assess whether the value might be boolean instead of a Result object. For a better idea of what queries are write-type queries, check `BaseConnection::isWriteType()` and any DBMS-specific override `isWriteType()` in the relevant Connection class.
+
+## Breaking Enhancements
+
+### ConnectionInterface::isWriteType() Declaration Added
+
+If you have written any classes that implement ConnectionInterface, these must now implement the `isWriteType()` method, declared as `public function isWriteType($sql): bool`. If your class extends BaseConnection, then that class will provide a basic `isWriteType()` method which you might want to override.
+
+### Test Traits
+
+The `CodeIgniter\Test` namespace has had significant improvements to help developers with their own test cases. Most notably, test extensions have moved to Traits to make them easier to pick-and-choose across various test case needs. The `CIDatabaseTestCase` and `FeatureTestCase` classes have been deprecated and their methods moved to `DatabaseTestTrait` and `FeatureTestTrait` respectively. Update your test cases to extend the main test case and use any traits you need. For example:
+
+```php
+response()->getBody();`
+
+## Project Files
+
+Numerous files in the project space (root, app, public, writable) received updates. Due to these files being outside of the system scope, they will not be changed without your intervention. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+!!! note "Note"
+ Except in very rare cases for bug fixes, no changes made to files for the project space will break your application. All changes noted here are optional until the next major version, and any mandatory changes will be covered in the sections above.
+
+### 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:
+
+- `app/Config/App.php`
+- `app/Config/Autoload.php`
+- `app/Config/Cookie.php`
+- `app/Config/Events.php`
+- `app/Config/Exceptions.php`
+- `app/Config/Security.php`
+- `app/Views/errors/html/*`
+- `env`
+- `spark`
+
+### 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:
+
+- `app/Config/App.php`
+- `app/Config/Autoload.php`
+- `app/Config/ContentSecurityPolicy.php`
+- `app/Config/Cookie.php`
+- `app/Config/Events.php`
+- `app/Config/Exceptions.php`
+- `app/Config/Logger.php`
+- `app/Config/Mimes.php`
+- `app/Config/Modules.php`
+- `app/Config/Security.php`
+- `app/Controllers/BaseController.php`
+- `app/Views/errors/html/debug.css`
+- `app/Views/errors/html/error_404.php`
+- `app/Views/errors/html/error_exception.php`
+- `app/Views/welcome_message.php`
+- `composer.json`
+- `contributing/guidelines.rst`
+- `env`
+- `public/.htaccess`
+- `public/index.php`
+- `spark`
diff --git a/docs/installation/upgrade_413.md b/docs/installation/upgrade_413.md
new file mode 100644
index 000000000000..200b4bb7e474
--- /dev/null
+++ b/docs/installation/upgrade_413.md
@@ -0,0 +1,20 @@
+# Upgrading from 4.1.2 to 4.1.3
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Enhancements
+
+### Cache TTL
+
+There is a new value in **app/Config/Cache.php**: `$ttl`. This is not used by framework handlers where 60 seconds is hard-coded, but may be useful to projects and modules. In a future release this value will replace the hard-coded version, so either leave this as `60` or stop relying on the hard-coded version.
+
+## Project Files
+
+Only a few 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. The following files received changes and it is recommended that you merge the updated versions with your application:
+
+- `app/Config/Cache.php`
+- `spark`
diff --git a/docs/installation/upgrade_414.md b/docs/installation/upgrade_414.md
new file mode 100644
index 000000000000..02b410738818
--- /dev/null
+++ b/docs/installation/upgrade_414.md
@@ -0,0 +1,43 @@
+# Upgrading from 4.1.3 to 4.1.4
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+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).
+
+## Breaking Changes
+
+### Method Scope
+
+The following methods were changed from `public` to `protected` to match their parent class methods and better align with their uses. If you relied on any of these methods being public (highly unlikely), adjust your code accordingly:
+
+- `CodeIgniter\Database\MySQLi\Connection::execute()`
+- `CodeIgniter\Database\MySQLi\Connection::_fieldData()`
+- `CodeIgniter\Database\MySQLi\Connection::_indexData()`
+- `CodeIgniter\Database\MySQLi\Connection::_foreignKeyData()`
+- `CodeIgniter\Database\Postgre\Builder::_like_statement()`
+- `CodeIgniter\Database\Postgre\Connection::execute()`
+- `CodeIgniter\Database\Postgre\Connection::_fieldData()`
+- `CodeIgniter\Database\Postgre\Connection::_indexData()`
+- `CodeIgniter\Database\Postgre\Connection::_foreignKeyData()`
+- `CodeIgniter\Database\SQLSRV\Connection::execute()`
+- `CodeIgniter\Database\SQLSRV\Connection::_fieldData()`
+- `CodeIgniter\Database\SQLSRV\Connection::_indexData()`
+- `CodeIgniter\Database\SQLSRV\Connection::_foreignKeyData()`
+- `CodeIgniter\Database\SQLite3\Connection::execute()`
+- `CodeIgniter\Database\SQLite3\Connection::_fieldData()`
+- `CodeIgniter\Database\SQLite3\Connection::_indexData()`
+- `CodeIgniter\Database\SQLite3\Connection::_foreignKeyData()`
+- `CodeIgniter\Images\Handlers\GDHandler::_flatten()`
+- `CodeIgniter\Images\Handlers\GDHandler::_flip()`
+- `CodeIgniter\Images\Handlers\ImageMagickHandler::_flatten()`
+- `CodeIgniter\Images\Handlers\ImageMagickHandler::_flip()`
+- `CodeIgniter\Test\Mock\MockIncomingRequest::detectURI()`
+- `CodeIgniter\Test\Mock\MockSecurity::sendCookie()`
+
+## Project Files
+
+All files in the project space were reformatted with the new coding style. This will not affect existing code but you may want to apply the updated coding style to your own projects to keep them in line with the framework's version of these files.
diff --git a/docs/installation/upgrade_415.md b/docs/installation/upgrade_415.md
new file mode 100644
index 000000000000..38e6bedb4e33
--- /dev/null
+++ b/docs/installation/upgrade_415.md
@@ -0,0 +1,133 @@
+# Upgrading from 4.1.4 to 4.1.5
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Changes for set() method in BaseBuilder and Model class
+
+The casting for the `$value` parameter has been removed to fix a bug where passing parameters as an array and as a string to the `set()` method was handled differently. If you extended the `BaseBuilder` class or `Model` class yourself and modified the `set()` method, then you need to change its definition from `public function set($key, ?string $value = '', ?bool $escape = null)` to `public function set($key, $value = '', ?bool $escape = null)`.
+
+### Session DatabaseHandler's Database Table Change
+
+The types of the following columns in the session table have been changed for optimization.
+
+- MySQL
+ - `timestamp`
+- PostgreSQL
+ - `ip_address`
+ - `timestamp`
+ - `data`
+
+Update the definition of the session table. See the [Session Library](../libraries/sessions.md) for the new definition.
+
+The change was introduced in v4.1.2. But due to [a bug](https://github.com/codeigniter4/CodeIgniter4/issues/4807), the DatabaseHandler Driver did not work properly.
+
+### CSRF Protection
+
+Because of a bug fix, CSRF protection now works on not only **POST** but also **PUT/PATCH/DELETE** requests when the CSRF filter is applied.
+
+When you use **PUT/PATCH/DELETE** requests, you need to send the CSRF token. Or remove the CSRF filter for such requests if you don't need CSRF protection for them.
+
+If you want the same behavior as the previous version, set the CSRF filter like the following in **app/Config/Filters.php**:
+
+```php
+ ['csrf'],
+ 'POST' => ['csrf'],
+ ];
+ // ...
+}
+```
+
+Protecting the **GET** method is needed only when you use `form_open()` auto-generation of the CSRF field.
+
+!!! warning "Warning"
+ In general, if you use `$methods` filters, you should [disable Auto Routing (Legacy)](../incoming/routing.md#use-defined-routes-only) because [Auto Routing (Legacy)](../incoming/routing.md#auto-routing-legacy) permits any HTTP method to access a controller. Accessing the controller with a method you don't expect could bypass the filter.
+
+### CURLRequest Header Change
+
+In the previous version, if you didn't provide your own headers, `CURLRequest` would send the request-headers from the browser. The bug was fixed. If your requests depend on the headers, your requests might fail after upgrading. In this case, add the necessary headers manually. See [CURLRequest Class](../libraries/curlrequest.md#headers) for how to add them.
+
+### Query Builder Changes
+
+For optimization and a bug fix, the following behaviors, mostly used in testing, have been changed.
+
+- When you use `insertBatch()` and `updateBatch()`, the return value of `$query->getOriginalQuery()` has changed. It no longer returns the query with the bound parameters, but the actual query that was run.
+- If `testMode` is `true`, `insertBatch()` will return an SQL string array instead of the number of affected rows. This change was made so that the returned data type is the same as the `updateBatch()` method.
+
+## Breaking Enhancements
+
+### Multiple Filters for a Route
+
+A new feature has been added to set multiple filters for a route.
+
+!!! important "Important"
+ This feature is disabled by default because it breaks backward compatibility.
+
+If you want to use this, you need to set the property `$multipleFilters` to `true` in `app/Config/Feature.php`. If you enable it:
+
+- `CodeIgniter\CodeIgniter::handleRequest()` uses
+ - `CodeIgniter\Filters\Filters::enableFilters()`, instead of `enableFilter()`
+- `CodeIgniter\CodeIgniter::tryToRouteIt()` uses
+ - `CodeIgniter\Router\Router::getFilters()`, instead of `getFilter()`
+- `CodeIgniter\Router\Router::handle()` uses
+ - the property `$filtersInfo`, instead of `$filterInfo`
+ - `CodeIgniter\Router\RouteCollection::getFiltersForRoute()`, instead of `getFilterForRoute()`
+
+If you extended the above classes, then you need to change them.
+
+The following methods and a property have been deprecated:
+
+- `CodeIgniter\Filters\Filters::enableFilter()`
+- `CodeIgniter\Router\Router::getFilter()`
+- `CodeIgniter\Router\RouteCollection::getFilterForRoute()`
+- `CodeIgniter\Router\RouteCollection`'s property `$filterInfo`
+
+See [Applying Filters](../incoming/routing.md#applying-filters) for the functionality.
+
+## Project Files
+
+Numerous files in the project space (root, app, public, writable) received updates. Due to these files being outside of the system scope, they will not be changed without your intervention. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+!!! note "Note"
+ Except in very rare cases for bug fixes, no changes made to files for the project space will break your application. All changes noted here are optional until the next major version, and any mandatory changes will be covered in the sections above.
+
+### 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:
+
+- `app/Config/CURLRequest.php`
+- `app/Config/Cache.php`
+- `app/Config/Feature.php`
+- `app/Config/Generators.php`
+- `app/Config/Publisher.php`
+- `app/Config/Security.php`
+- `app/Views/welcome_message.php`
+
+### 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:
+
+- `app/Config/CURLRequest.php`
+- `app/Config/Cache.php`
+- `app/Config/Feature.php`
+- `app/Config/Generators.php`
+- `app/Config/Kint.php`
+- `app/Config/Publisher.php`
+- `app/Config/Security.php`
+- `app/Views/welcome_message.php`
diff --git a/docs/installation/upgrade_416.md b/docs/installation/upgrade_416.md
new file mode 100644
index 000000000000..9de43a67fb6e
--- /dev/null
+++ b/docs/installation/upgrade_416.md
@@ -0,0 +1,58 @@
+# Upgrading from 4.1.5 to 4.1.6
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Validation Result Changes
+
+Due to a bug fix, Validation might now change the validation results when you validate an array item (see [Changelog](../changelogs/v4.1.6.md#validation-changes)). So check the validation results for all the code that validates the array. Validating multiple fields like `contacts.*.name` is not affected.
+
+If you have the following form:
+
+```
+
+
+```
+
+And you have the validation rule like this:
+
+```
+'invoice_rule' => ['rules' => 'numeric', 'errors' => ['numeric' => 'Not numeric']]
+```
+
+Change the rule key to `invoice_rule.*` and the validation will work.
+
+## Breaking Enhancements
+
+None.
+
+## Project Files
+
+Numerous files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+!!! note "Note"
+ Except in very rare cases for bug fixes, no changes made to files for the project space will break your application. All changes noted here are optional until the next major version, and any mandatory changes will be covered in the sections above.
+
+### 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:
+
+- `app/Config/Filters.php`
+- `app/Config/Mimes.php`
+- `app/Config/Security.php`
+- `app/Config/Toolbar.php`
+
+### 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:
+
+- `app/Config/Filters.php`
+- `app/Config/Mimes.php`
+- `app/Config/Security.php`
+- `app/Config/Toolbar.php`
+- `app/Views/errors/html/error_exception.php`
diff --git a/docs/installation/upgrade_417.md b/docs/installation/upgrade_417.md
new file mode 100644
index 000000000000..2c44f0db70b4
--- /dev/null
+++ b/docs/installation/upgrade_417.md
@@ -0,0 +1,11 @@
+# Upgrading from 4.1.6 to 4.1.7
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+- The output of `get_cookie()` when `$xssClean` is true has changed. Now it uses `FILTER_SANITIZE_FULL_SPECIAL_CHARS`, not `FILTER_SANITIZE_STRING`. Make sure the change is acceptable. Note that using XSS filtering is a bad practice. It does not prevent XSS attacks perfectly. Using `esc()` with the correct `$context` in the views is recommended.
diff --git a/docs/installation/upgrade_418.md b/docs/installation/upgrade_418.md
new file mode 100644
index 000000000000..4a78ff2c5844
--- /dev/null
+++ b/docs/installation/upgrade_418.md
@@ -0,0 +1,11 @@
+# Upgrading from 4.1.7 to 4.1.8
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+- Due to a security issue in the `API\ResponseTrait`, all trait methods are now scoped to `protected`. See the [Security advisory GHSA-7528-7jg5-6g62](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-7528-7jg5-6g62) for more information.
diff --git a/docs/installation/upgrade_420.md b/docs/installation/upgrade_420.md
new file mode 100644
index 000000000000..6b6238df1e14
--- /dev/null
+++ b/docs/installation/upgrade_420.md
@@ -0,0 +1,105 @@
+# Upgrading from 4.1.9 to 4.2.0
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Mandatory File Changes
+
+### index.php and spark
+
+The following files received significant changes and **you must merge the updated versions** with your application:
+
+- `public/index.php`
+- `spark`
+
+!!! important "Important"
+ If you don't update the above two files, CodeIgniter will not work at all after running `composer update`.
+
+ The upgrade procedure, for example, is as follows:
+
+ ```console
+ composer update
+ cp vendor/codeigniter4/framework/public/index.php public/index.php
+ cp vendor/codeigniter4/framework/spark .
+ ```
+
+### Config/Constants.php
+
+The constants `EVENT_PRIORITY_LOW`, `EVENT_PRIORITY_NORMAL` and `EVENT_PRIORITY_HIGH` are deprecated, and the definitions are moved to `app/Config/Constants.php`. If you use these constants, define them in `app/Config/Constants.php`. Or use the new class constants `CodeIgniter\Events\Events::PRIORITY_LOW`, `CodeIgniter\Events\Events::PRIORITY_NORMAL` and `CodeIgniter\Events\Events::PRIORITY_HIGH`.
+
+### composer.json
+
+!!! note "Note"
+ This procedure is not required in v4.5.0 or later.
+
+If you use Composer, installed CodeIgniter v4.1.9 or earlier, and there are `App\\` and `Config\\` namespaces in your `/composer.json`'s `autoload.psr-4` like the following, you need to remove these lines, and run `composer dump-autoload`.
+
+```text
+{
+ ...
+ "autoload": {
+ "psr-4": {
+ "App\\": "app", <-- Remove this line
+ "Config\\": "app/Config" <-- Remove this line
+ }
+ },
+ ...
+}
+```
+
+## Breaking Changes
+
+- The `system/bootstrap.php` file no longer returns a `CodeIgniter` instance, and does not load the `.env` file (now handled in `index.php` and `spark`). If you have code that expects these behaviors, it will no longer work and must be modified. This has been changed to make [Preloading](https://www.php.net/manual/en/opcache.preloading.php) easier to implement.
+
+## Breaking Enhancements
+
+- The method signature of `Validation::setRule()` has been changed. The `string` typehint on the `$rules` parameter was removed. Extending classes should likewise remove the typehint so as not to break LSP.
+- The method signatures of `CodeIgniter\Database\BaseBuilder::join()` and `CodeIgniter\Database\*\Builder::join()` have been changed. The `string` typehint on the `$cond` parameter was removed. Extending classes should likewise remove the typehint so as not to break LSP.
+
+## Project Files
+
+Numerous files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+!!! note "Note"
+ Except in very rare cases for bug fixes, no changes made to files for the project space will break your application. All changes noted here are optional until the next major version, and any mandatory changes will be covered in the sections above.
+
+### 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:
+
+- `app/Config/Routes.php`
+ - To make the default configuration more secure, auto-routing has been changed to disabled by default.
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Constants.php**
+- **app/Config/ContentSecurityPolicy.php**
+- **app/Config/Database.php**
+- **app/Config/Events.php**
+- **app/Config/Feature.php**
+- **app/Config/Filters.php**
+- **app/Config/Format.php**
+- **app/Config/Logger.php**
+- **app/Config/Mimes.php**
+- **app/Config/Publisher.php**
+- **app/Config/Routes.php**
+- **app/Config/Security.php**
+- **app/Config/Validation.php**
+- **app/Config/View.php**
+- **app/Controllers/BaseController.php**
+- **app/Views/errors/html/debug.css**
+- **app/Views/errors/html/debug.js**
+- **app/Views/errors/html/error_404.php**
+- **app/Views/errors/html/error_exception.php**
+- **app/Views/errors/html/production.php**
+- **app/Views/welcome_message.php**
+- **app/index.html**
+- **preload.php**
+- **public/index.php**
+- spark
diff --git a/docs/installation/upgrade_421.md b/docs/installation/upgrade_421.md
new file mode 100644
index 000000000000..03c3170a0dc4
--- /dev/null
+++ b/docs/installation/upgrade_421.md
@@ -0,0 +1,65 @@
+# Upgrading from 4.2.0 to 4.2.1
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Mandatory File Changes
+
+### app/Config/Mimes.php
+
+- The mapping of file extensions to MIME types in **app/Config/Mimes.php** was updated to fix a bug. Also, the logic of `Mimes::getExtensionFromType()` was changed.
+
+## Breaking Changes
+
+### get_cookie()
+
+If there is a cookie with a prefixed name and a cookie with the same name without a prefix, the previous `get_cookie()` had the tricky behavior of returning the cookie without the prefix.
+
+For example, when `Config\Cookie::$prefix` is `prefix_` and there are two cookies, `test` and `prefix_test`:
+
+```php
+$_COOKIES = [
+ 'test' => 'Non CI Cookie',
+ 'prefix_test' => 'CI Cookie',
+];
+```
+
+Previously, `get_cookie()` returned the following:
+
+```php
+get_cookie('test'); // returns "Non CI Cookie"
+get_cookie('prefix_test'); // returns "CI Cookie"
+```
+
+Now this bug has been fixed, and the behavior has changed as follows:
+
+```php
+get_cookie('test'); // returns "CI Cookie"
+get_cookie('prefix_test'); // returns null
+get_cookie('test', false, null); // returns "Non CI Cookie"
+```
+
+If you depend on the previous behavior, you need to change your code.
+
+!!! note "Note"
+ In the example above, if there is only one cookie `prefix_test`, the previous `get_cookie('test')` also returned `"CI Cookie"`.
+
+## Breaking Enhancements
+
+## Project Files
+
+Numerous files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+!!! note "Note"
+ Except in very rare cases for bug fixes, no changes made to files for the project space will break your application. All changes noted here are optional until the next major version, and any mandatory changes will be covered in the sections above.
+
+### Content Changes
+
+### 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:
+
+- **app/Config/Mimes.php**
diff --git a/docs/installation/upgrade_4210.md b/docs/installation/upgrade_4210.md
new file mode 100644
index 000000000000..fad3a3cbf582
--- /dev/null
+++ b/docs/installation/upgrade_4210.md
@@ -0,0 +1,17 @@
+# Upgrading from 4.2.9 to 4.2.10
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Version 4.2.10 did not alter any executable code in project files.
+
+### 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:
+
+- **composer.json**
diff --git a/docs/installation/upgrade_4211.md b/docs/installation/upgrade_4211.md
new file mode 100644
index 000000000000..9aefffd38b31
--- /dev/null
+++ b/docs/installation/upgrade_4211.md
@@ -0,0 +1,50 @@
+# Upgrading from 4.2.10 to 4.2.11
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Config\App::$proxyIPs
+
+The config value format has been changed. Now you must set your proxy IP address and the HTTP header name for the client IP address pair as an array:
+
+```
+public $proxyIPs = [
+ '10.0.1.200' => 'X-Forwarded-For',
+ '192.168.5.0/24' => 'X-Forwarded-For',
+];
+```
+
+`ConfigException` will be thrown for a config value in the old format.
+
+### Session Handler Key Changes
+
+The key of the session data record for [DatabaseHandler Driver](../libraries/sessions.md#databasehandler-driver), [MemcachedHandler Driver](../libraries/sessions.md#memcachedhandler-driver) and [RedisHandler Driver](../libraries/sessions.md#redishandler-driver) has changed. Therefore, any existing session data will be invalidated after the upgrade if you are using these session handlers.
+
+- When using `DatabaseHandler`, the `id` column value in the session table now contains the session cookie name (`Config\App::$sessionCookieName`).
+- When using `MemcachedHandler` or `RedisHandler`, the key value contains the session cookie name (`Config\App::$sessionCookieName`).
+
+There is a maximum length for the `id` column and Memcached key (250 bytes). If the following values exceed those maximum lengths, the session will not work properly.
+
+- the session cookie name, delimiter, and session id (32 characters by default) when using `DatabaseHandler`
+- the prefix (`ci_session`), session cookie name, delimiters, and session id when using `MemcachedHandler`
+
+## Project Files
+
+Version 4.2.11 did not alter any executable code in project files.
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Autoload.php**
+- **app/Config/Logger.php**
+- **app/Config/Toolbar.php**
+- **app/Views/welcome_message.php**
+- **composer.json**
+- **phpunit.xml.dist**
diff --git a/docs/installation/upgrade_4212.md b/docs/installation/upgrade_4212.md
new file mode 100644
index 000000000000..dba83826fe25
--- /dev/null
+++ b/docs/installation/upgrade_4212.md
@@ -0,0 +1,20 @@
+# Upgrading from 4.2.11 to 4.2.12
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Version 4.2.12 did not alter any executable code in project files.
+
+### 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:
+
+- **app/Config/Cache.php**
+- **app/Config/Migrations.php**
+- **app/Controllers/BaseController.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_422.md b/docs/installation/upgrade_422.md
new file mode 100644
index 000000000000..e6b669349922
--- /dev/null
+++ b/docs/installation/upgrade_422.md
@@ -0,0 +1,50 @@
+# Upgrading from 4.2.1 to 4.2.2
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Web Page Caching Bug Fix
+
+- [Web Page Caching](../general/caching.md) now caches the Response data after [After Filters](../incoming/filters.md#after-filters) are executed.
+- For example, if you enable [SecureHeaders](../incoming/filters.md#secureheaders), the Response headers are now sent when the page comes from the cache.
+
+!!! important "Important"
+ If you have written **code based on this bug** that assumes changes to the Response in "after" filters are not cached then **sensitive information could be cached and compromised**. If this is the case, change your code to disable caching of the page.
+
+### Others
+
+- The method `Forge::createTable()` no longer executes a `CREATE TABLE IF NOT EXISTS`. When `$ifNotExists` is true, if the table is not found in `$db->tableExists($table)` then `CREATE TABLE` is executed.
+- The second parameter `$ifNotExists` of `Forge::_createTable()` is deprecated. It is no longer used and will be removed in a future release.
+- When you use `random_string()` with the first parameter `'crypto'`, if you set the second parameter `$len` to an odd number, `InvalidArgumentException` will now be thrown. Change the parameter to an even number.
+
+## Breaking Enhancements
+
+## Project Files
+
+Numerous files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+!!! note "Note"
+ Except in very rare cases for bug fixes, no changes made to files for the project space will break your application. All changes noted here are optional until the next major version, and any mandatory changes will be covered in the sections above.
+
+### Content Changes
+
+- **app/Views/errors/html/error_404.php**
+- **app/Views/welcome_message.php**
+- **public/index.php**
+- spark
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Constants.php**
+- **app/Config/Logger.php**
+- **app/Config/Paths.php**
+- **app/Views/errors/html/error_404.php**
+- **app/Views/welcome_message.php**
diff --git a/docs/installation/upgrade_423.md b/docs/installation/upgrade_423.md
new file mode 100644
index 000000000000..ad961f697834
--- /dev/null
+++ b/docs/installation/upgrade_423.md
@@ -0,0 +1,11 @@
+# Upgrading from 4.2.2 to 4.2.3
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Version 4.2.3 is an internal change for security measures and requires no intervention in projects.
diff --git a/docs/installation/upgrade_425.md b/docs/installation/upgrade_425.md
new file mode 100644
index 000000000000..64df579e4e55
--- /dev/null
+++ b/docs/installation/upgrade_425.md
@@ -0,0 +1,11 @@
+# Upgrading from 4.2.3 to 4.2.5
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Version 4.2.5 did not alter any project files.
diff --git a/docs/installation/upgrade_426.md b/docs/installation/upgrade_426.md
new file mode 100644
index 000000000000..c377e0080930
--- /dev/null
+++ b/docs/installation/upgrade_426.md
@@ -0,0 +1,20 @@
+# Upgrading from 4.2.5 to 4.2.6
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+A few files in the **project space** (root, app, public, writable) received cosmetic updates. You need not touch these files at all. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/ContentSecurityPolicy.php**
+- **app/Config/Routes.php**
+- **app/Config/Validation.php**
diff --git a/docs/installation/upgrade_427.md b/docs/installation/upgrade_427.md
new file mode 100644
index 000000000000..dd06594d4773
--- /dev/null
+++ b/docs/installation/upgrade_427.md
@@ -0,0 +1,56 @@
+# Upgrading from 4.2.6 to 4.2.7
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### set_cookie()
+
+Due to a bug, `set_cookie()` and `CodeIgniter\HTTP\Response::setCookie()` in the previous versions did not use the `$secure` and `$httponly` values in `Config\Cookie`. The following code did not issue a cookie with the secure flag even if you set `$secure = true` in `Config\Cookie`:
+
+```
+helper('cookie');
+
+$cookie = [
+ 'name' => $name,
+ 'value' => $value,
+];
+set_cookie($cookie);
+// or
+$this->response->setCookie($cookie);
+```
+
+But now the values in `Config\Cookie` are used for the options that are not specified. The above code issues a cookie with the secure flag if you set `$secure = true` in `Config\Cookie`.
+
+If your code depends on this bug, please change it to explicitly specify the necessary options:
+
+```
+$cookie = [
+ 'name' => $name,
+ 'value' => $value,
+ 'secure' => false, // Set explicitly
+ 'httponly' => false, // Set explicitly
+];
+set_cookie($cookie);
+// or
+$this->response->setCookie($cookie);
+```
+
+### Others
+
+- `Time::__toString()` is now locale-independent. It returns database-compatible strings like '2022-09-07 12:00:00' in any locale. Most locales are not affected by this change. But in a few locales like `ar`, `fa`, `Time::__toString()` (or `(string) $time` or implicit casting to a string) no longer returns a localized datetime string. If you want to get a localized datetime string, use [Time::toDateTimeString()](../libraries/time.md#todatetimestring) instead.
+- The logic of Validation rule `required_without` has been changed to validate each array item separately when validating fields with asterisk (`*`), and the method signature of the rule method has also been changed. Extending classes should likewise update the parameters so as not to break LSP.
+
+## Project Files
+
+Version 4.2.7 did not alter any executable code in project files.
+
+### 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:
+
+- **app/Common.php**
diff --git a/docs/installation/upgrade_428.md b/docs/installation/upgrade_428.md
new file mode 100644
index 000000000000..a155a6f409e0
--- /dev/null
+++ b/docs/installation/upgrade_428.md
@@ -0,0 +1,20 @@
+# Upgrading from 4.2.7 to 4.2.8
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+The following files had changes to code or behavior that merit updating in your projects:
+
+- **app/Views/errors/html/error_exception.php**
+
+### 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:
+
+- **app/Config/Logger.php**
+- **app/Views/errors/html/error_exception.php**
diff --git a/docs/installation/upgrade_430.md b/docs/installation/upgrade_430.md
new file mode 100644
index 000000000000..caacc993557a
--- /dev/null
+++ b/docs/installation/upgrade_430.md
@@ -0,0 +1,345 @@
+# Upgrading from 4.2.12 to 4.3.0
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Composer Version
+
+!!! important "Important"
+ If you use Composer, CodeIgniter v4.3.0 requires Composer 2.0.14 or later.
+
+If you are using an older version of Composer, upgrade your `composer` tool, and delete the **vendor/** directory, and run `composer update` again.
+
+The procedure, for example, is as follows:
+
+```console
+composer self-update
+rm -rf vendor/
+composer update
+```
+
+## Mandatory File Changes
+
+### spark
+
+The following files received significant changes and **you must merge the updated versions** with your application:
+
+- `spark`
+
+!!! important "Important"
+ If you do not update this file, Spark commands will not work at all after running `composer update`.
+
+ The upgrade procedure, for example, is as follows:
+
+ ```console
+ composer update
+ cp vendor/codeigniter4/framework/spark .
+ ```
+
+### Config Files
+
+#### app/Config/Kint.php
+
+- **app/Config/Kint.php** has been updated for Kint 5.0.
+- You need to replace:
+ - `Kint\Renderer\Renderer` with `Kint\Renderer\AbstractRenderer`
+ - `Renderer::SORT_FULL` with `AbstractRenderer::SORT_FULL`
+
+#### app/Config/Exceptions.php
+
+- If you are using PHP 8.2, you need to add new properties `$logDeprecations` and `$deprecationLogLevel`.
+
+#### Mock Config Classes
+
+- If you are using the following Mock Config classes in testing, you need to update the corresponding Config files in **app/Config**:
+ - `MockAppConfig` (`Config\App`)
+ - `MockCLIConfig` (`Config\App`)
+ - `MockSecurityConfig` (`Config\Security`)
+- Add **types** to the properties in these Config classes. You may need to fix the property values to match the property types.
+
+### composer.json
+
+If you installed CodeIgniter manually, and are using Composer, you need to remove the following lines, and run `composer update`.
+
+```text
+{
+ ...
+ "require": {
+ ...
+ "kint-php/kint": "^4.2", <-- Remove this line
+ ...
+ },
+ ...
+ "scripts": {
+ "post-update-cmd": [
+ "CodeIgniter\\ComposerScripts::postUpdate" <-- Remove this line
+ ],
+ "test": "phpunit"
+ },
+ ...
+}
+```
+
+## Breaking Changes
+
+### Database Exception Changes
+
+- The exception classes may be changed when database errors occur. If you catch the exceptions, you must confirm that your code can catch the exceptions.
+- Now a few exceptions will be thrown even if `CI_DEBUG` is false.
+- During transactions, exceptions are not thrown by default even if `DBDebug` is true. If you want exceptions to be thrown, you need to call `transException(true)`. See [Throwing Exceptions](../database/transactions.md#throwing-exceptions).
+- See [Exceptions when Database Errors Occur](../changelogs/v4.3.0.md#exceptions-when-database-errors-occur) for details.
+
+### HTTP Status Code and Exit Code of Uncaught Exceptions
+
+- If you expect *Exception code* as *HTTP status code*, the HTTP status code will be changed. In that case, you need to implement `HTTPExceptionInterface` in the Exception. See [Specify HTTP Status Code in Your Exception](../general/errors.md#specify-http-status-code-in-your-exception).
+- If you expect *Exit code* based on *Exception code*, the Exit code will be changed. In that case, you need to implement `HasExitCodeInterface` in the Exception. See [Specify Exit Code in Your Exception](../general/errors.md#specify-exit-code-in-your-exception).
+
+### redirect()->withInput() and Validation Errors
+
+`redirect()->withInput()` and Validation errors had an undocumented behavior. If you redirect with `withInput()`, CodeIgniter stores the validation errors in the session, and you can get the errors in the redirected page from a validation object *before a new validation is run*:
+
+```
+// In the controller
+if (! $this->validate($rules)) {
+ return redirect()->back()->withInput();
+}
+
+// In the view of the redirected page
+= service('Validation')->listErrors() ?>
+```
+
+This behavior was a bug and was fixed in v4.3.0.
+
+If you have code that depends on the bug, you need to change the code. Use the new Form helpers, `validation_errors()`, `validation_list_errors()` and `validation_show_error()` to display Validation Errors, instead of the Validation object.
+
+### Validation Changes
+
+- `ValidationInterface` has been changed. Implementing classes should likewise add the methods and the parameters so as not to break LSP. See [Validation Changes](../changelogs/v4.3.0.md#validation-changes) for details.
+- The return value of `Validation::loadRuleGroup()` has been changed from `null` to `[]` when the `$group` is empty. Update the code if you depend on the behavior.
+
+### Time Fixes
+
+- Due to bug fixes, some methods in [Time](../libraries/time.md) have changed from mutable behavior to immutable; `Time` now extends `DateTimeImmutable`. See [ChangeLog](../changelogs/v4.3.0.md#time) for details.
+- If you need the behavior of `Time` before the modification, a compatible `TimeLegacy` class has been added. Please replace all `Time` with `TimeLegacy` in your application code.
+- But `TimeLegacy` is deprecated. So we recommend you update your code.
+
+E.g.:
+
+```
+// Before
+$time = Time::now();
+// ...
+if ($time instanceof DateTime) {
+ // ...
+}
+
+// After
+$time = Time::now();
+// ...
+if ($time instanceof DateTimeInterface) {
+ // ...
+}
+```
+
+```
+// Before
+$time1 = new Time('2022-10-31 12:00');
+$time2 = $time1->modify('+1 day');
+echo $time1; // 2022-11-01 12:00:00
+echo $time2; // 2022-11-01 12:00:00
+
+// After
+$time1 = new Time('2022-10-31 12:00');
+$time2 = $time1->modify('+1 day');
+echo $time1; // 2022-10-31 12:00:00
+echo $time2; // 2022-11-01 12:00:00
+```
+
+### Capturing STDERR and STDOUT streams in Tests
+
+The way error and output streams are captured has changed. Now instead of:
+
+```
+use CodeIgniter\Test\Filters\CITestStreamFilter;
+
+protected function setUp(): void
+{
+ CITestStreamFilter::$buffer = '';
+ $this->streamFilter = stream_filter_append(STDOUT, 'CITestStreamFilter');
+ $this->streamFilter = stream_filter_append(STDERR, 'CITestStreamFilter');
+}
+
+protected function tearDown(): void
+{
+ stream_filter_remove($this->streamFilter);
+}
+```
+
+you need to use:
+
+```
+use CodeIgniter\Test\Filters\CITestStreamFilter;
+
+protected function setUp(): void
+{
+ CITestStreamFilter::registration();
+ CITestStreamFilter::addOutputFilter();
+ CITestStreamFilter::addErrorFilter();
+}
+
+protected function tearDown(): void
+{
+ CITestStreamFilter::removeOutputFilter();
+ CITestStreamFilter::removeErrorFilter();
+}
+```
+
+Or use the trait `CodeIgniter\Test\StreamFilterTrait`. See [Testing CLI Output](../testing/cli.md#testing-cli-output).
+
+### Interface Changes
+
+Some interfaces have been fixed. See [Interface Changes](../changelogs/v4.3.0.md#interface-changes) for details.
+
+### Foreign Key Data
+
+- The data structure returned by `BaseConnection::getForeignKeyData()` has been changed. You will need to adjust any code depending on this method to use the new structure.
+
+Example: `tableprefix_table_column1_column2_foreign`
+
+The data returned has the following structure:
+
+```
+/**
+ * @return array[
+ * {constraint_name} =>
+ * stdClass[
+ * 'constraint_name' => string,
+ * 'table_name' => string,
+ * 'column_name' => string[],
+ * 'foreign_table_name' => string,
+ * 'foreign_column_name' => string[],
+ * 'on_delete' => string,
+ * 'on_update' => string,
+ * 'match' => string
+ * ]
+ * ]
+ */
+```
+
+## Breaking Enhancements
+
+### Multiple Domain Support
+
+- If you set `Config\App::$allowedHostnames`, URL-related functions such as `base_url()`, `current_url()`, `site_url()` will return the URL with the hostname set in `Config\App::$allowedHostnames` if the current URL matches.
+
+### Database
+
+- The return type of `CodeIgniter\Database\Database::loadForge()` has been changed to `Forge`. Extending classes should likewise change the type.
+- The return type of `CodeIgniter\Database\Database::loadUtils()` has been changed to `BaseUtils`. Extending classes should likewise change the type.
+- The second parameter `$index` of `BaseBuilder::updateBatch()` has changed to `$constraints`. It now accepts types array, string, or `RawSql`. Extending classes should likewise change types.
+- The `$set` parameter of `BaseBuilder::insertBatch()` and `BaseBuilder::updateBatch()` now accepts an object of a single row of data. Extending classes should likewise change the type.
+- The third parameter `$index` of `BaseBuilder::_updateBatch()` has changed to `$values`, and the parameter type has changed to `array`. Extending classes should likewise change the type.
+- The `Model::update()` method now raises a `DatabaseException` if it generates an SQL statement without a WHERE clause. If you need to update all records in a table, use Query Builder instead. E.g., `$model->builder()->update($data)`.
+
+### Honeypot and CSP
+
+When CSP is enabled, the id attribute `id="hpc"` will be injected into the container tag for the Honeypot field to hide the field. If the id is already used in your views, you need to change it with `Config\Honeypot::$containerId`. And you can remove `style="display:none"` in `Config\Honeypot::$container`.
+
+### Others
+
+- **Helper:** Since void HTML elements (e.g. ` `) in `html_helper`, `form_helper` or common functions have been changed to be HTML5-compatible by default, if you need to be compatible with XHTML, you must set the `$html5` property in **app/Config/DocTypes.php** to `false`.
+- **CLI:** Since the launch of Spark Commands was extracted from `CodeIgniter\CodeIgniter`, there may be problems running these commands if the `Services::codeigniter()` service has been overridden.
+
+## Project Files
+
+Numerous files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention. There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/App.php**
+ - The new property `$allowedHostnames` is added to set allowed hostnames in the site URL other than the hostname in the `$baseURL`. See [Multiple Domain Support](../changelogs/v4.3.0.md#multiple-domain-support).
+ - The property `$appTimezone` has been changed to `UTC` to avoid being affected by daylight saving time.
+- **app/Config/Autoload.php**
+ - The new property `$helpers` is added to autoload helpers.
+- **app/Config/Database.php**
+ - `$default['DBDebug']` and `$test['DBDebug']` are changed to `true` by default. See [Exceptions when Database Errors Occur](../changelogs/v4.3.0.md#exceptions-when-database-errors-occur).
+- **app/Config/DocTypes.php**
+ - The property `$html5` to determine whether to remove the solidus (`/`) character for void HTML elements (e.g. ` `) is added, and set to `true` by default for HTML5 compatibility.
+- **app/Config/Encryption.php**
+ - The new properties `$rawData`, `$encryptKeyInfo`, and `$authKeyInfo` are added for CI3 Encryption compatibility. See [Configuration to Maintain Compatibility with CI3](../libraries/encryption.md#configuration-to-maintain-compatibility-with-ci3).
+- **app/Config/Exceptions.php**
+ - Two additional public properties were added: `$logDeprecations` and `$deprecationLogLevel`. See [Logging Deprecation Warnings](../general/errors.md#logging-deprecation-warnings) for details.
+- **app/Config/Honeypot.php**
+ - The new property `$containerId` is added to set the id attribute value for the container tag when CSP is enabled.
+ - The `input` tag in the property `$template` value has been changed to be HTML5-compatible.
+- **app/Config/Logger.php**
+ - The property `$threshold` has been changed to `9` in environments other than `production`.
+- **app/Config/Modules.php**
+ - The new property `$composerPackages` is added to limit Composer package Auto-Discovery for better performance.
+- **app/Config/Routes.php**
+ - Due to the fact that the approach to running Spark Commands has changed, there is no longer a need to load the internal routes of the framework (`SYSTEMPATH . 'Config/Routes.php'`).
+- **app/Config/Security.php**
+ - Changed the value of the property `$redirect` to `false` to prevent redirection when a CSRF check fails. This is to make it easier to recognize that it is a CSRF error.
+- **app/Config/Session.php**
+ - Added to handle session configuration.
+- **app/Config/Validation.php**
+ - The default Validation Rules have been changed to Strict Rules for better security. See [Traditional and Strict Rules](../libraries/validation.md#traditional-and-strict-rules).
+
+#### View Files
+
+The following view files have been changed to HTML5-compatible tags. Also, error messages are now defined in the **Errors** language file.
+
+- **app/Views/errors/html/error_404.php**
+- **app/Views/errors/html/error_exception.php**
+- **app/Views/errors/html/production.php**
+- **app/Views/welcome_message.php**
+
+### 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. All atomic type properties in `Config` classes have been typed:
+
+- **app/Config/App.php**
+- **app/Config/Autoload.php**
+- **app/Config/CURLRequest.php**
+- **app/Config/Cache.php**
+- **app/Config/ContentSecurityPolicy.php**
+- **app/Config/Cookie.php**
+- **app/Config/Database.php**
+- **app/Config/DocTypes.php**
+- **app/Config/Email.php**
+- **app/Config/Encryption.php**
+- **app/Config/Exceptions.php**
+- **app/Config/Feature.php**
+- **app/Config/Filters.php**
+- **app/Config/Format.php**
+- **app/Config/Generators.php**
+- **app/Config/Honeypot.php**
+- **app/Config/Images.php**
+- **app/Config/Kint.php**
+- **app/Config/Logger.php**
+- **app/Config/Migrations.php**
+- **app/Config/Mimes.php**
+- **app/Config/Modules.php**
+- **app/Config/Pager.php**
+- **app/Config/Paths.php**
+- **app/Config/Routes.php**
+- **app/Config/Security.php**
+- **app/Config/Session.php**
+- **app/Config/Toolbar.php**
+- **app/Config/UserAgents.php**
+- **app/Config/Validation.php**
+- **app/Views/errors/html/error_404.php**
+- **app/Views/errors/html/error_exception.php**
+- **app/Views/errors/html/production.php**
+- **app/Views/welcome_message.php**
+- **composer.json**
+- env
+- **phpunit.xml.dist**
+- spark
diff --git a/docs/installation/upgrade_431.md b/docs/installation/upgrade_431.md
new file mode 100644
index 000000000000..76aabac93f18
--- /dev/null
+++ b/docs/installation/upgrade_431.md
@@ -0,0 +1,57 @@
+# Upgrading from 4.3.0 to 4.3.1
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Composer Version
+
+!!! important "Important"
+ If you use Composer, CodeIgniter v4.3 requires Composer 2.0.14 or later.
+
+If you are using an older version of Composer, upgrade your `composer` tool, and delete the **vendor/** directory, and run `composer update` again.
+
+The procedure, for example, is as follows:
+
+```console
+composer self-update
+rm -rf vendor/
+composer update
+```
+
+## Mandatory File Changes
+
+### Config Files
+
+#### app/Config/Email.php
+
+- If you updated **app/Config/Email.php** when upgrading to v4.3.0, you must set the default values to `$fromEmail`, `$fromName`, `$recipients`, `$SMTPHost`, `$SMTPUser` and `$SMTPPass` to apply environment variable (**.env**) values.
+- If no default values are set, setting environment variables for them will not be reflected in the Config object.
+
+#### app/Config/Exceptions.php
+
+- If you are using PHP 8.2, you need to add new properties `$logDeprecations` and `$deprecationLogLevel`.
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Email.php**
+ - Set the default value `''` to `$fromEmail`, `$fromName`, `$recipients`, `$SMTPHost`, `$SMTPUser` and `$SMTPPass` to apply environment variable (**.env**) values.
+
+### 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:
+
+- **app/Config/Email.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_432.md b/docs/installation/upgrade_432.md
new file mode 100644
index 000000000000..5bf71f6bfadd
--- /dev/null
+++ b/docs/installation/upgrade_432.md
@@ -0,0 +1,71 @@
+# Upgrading from 4.3.1 to 4.3.2
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### base_url()
+
+The `base_url()` behavior has been fixed. In previous versions, when you called `base_url()` **without an argument**, it returned baseURL without a trailing slash (`/`). Now it returns baseURL with a trailing slash. For example:
+
+- before: `http://example.com`
+- after: `http://example.com/`
+
+If you have code that calls `base_url()` without an argument, you may need to adjust the URLs.
+
+### uri_string()
+
+The `uri_string()` behavior has been fixed. In previous versions, when you navigated to the baseURL, it returned `/`. Now it returns an empty string (`''`).
+
+If you have code that calls `uri_string()`, you may need to adjust it.
+
+!!! note "Note"
+ `uri_string()` returns a URI path relative to baseURL. It is not a full URI path if the baseURL contains subfolders. If you use it for HTML links, it is better to use it with `site_url()` like `site_url(uri_string())`.
+
+## Mandatory File Changes
+
+### composer.json
+
+If you have installed CodeIgniter manually and are using or planning to use Composer, remove the following line:
+
+```text
+{
+ ...
+ "scripts": {
+ "post-update-cmd": [
+ "CodeIgniter\\ComposerScripts::postUpdate" <-- Remove this line
+ ],
+ "test": "phpunit"
+ },
+ ...
+}
+```
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/Mimes.php**
+- **app/Views/errors/html/error_exception.php**
+- **composer.json**
+- **public/.htaccess**
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Mimes.php**
+- **app/Views/errors/html/error_exception.php**
+- **composer.json**
+- **public/.htaccess**
diff --git a/docs/installation/upgrade_433.md b/docs/installation/upgrade_433.md
new file mode 100644
index 000000000000..6ef41f9cd283
--- /dev/null
+++ b/docs/installation/upgrade_433.md
@@ -0,0 +1,30 @@
+# Upgrading from 4.3.2 to 4.3.3
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Encryption.php**
+ - The missing property `$cipher` is added for CI3 Encryption compatibility. See [Configuration to Maintain Compatibility with CI3](../libraries/encryption.md#configuration-to-maintain-compatibility-with-ci3).
+
+### 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:
+
+- **app/Common.php**
+- **app/Config/Encryption.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_434.md b/docs/installation/upgrade_434.md
new file mode 100644
index 000000000000..65ffc40a4efa
--- /dev/null
+++ b/docs/installation/upgrade_434.md
@@ -0,0 +1,42 @@
+# Upgrading from 4.3.3 to 4.3.4
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Redirect Status Code
+
+- Due to a bug fix, the status codes of redirects may be changed. See [ChangeLog v4.3.4](../changelogs/v4.3.4.md#redirect-status-code) and if the code is not what you want, [specify status codes](../outgoing/response.md#redirect-status-code).
+
+### Forge::modifyColumn() and NULL
+
+A bug fix may have changed the NULL constraint in the result of [$forge->modifyColumn()](../dbmgmt/forge.md#forge-modifycolumn). See [Change Log](../changelogs/v4.3.4.md#forgemodifycolumn). To set the desired NULL constraint, change `Forge::modifyColumn()` to always specify the `null` key.
+
+Note that the bug may have changed unexpected NULL constraints in previous versions.
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Generators.php**
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Generators.php**
+- **composer.json**
+- **public/index.php**
diff --git a/docs/installation/upgrade_435.md b/docs/installation/upgrade_435.md
new file mode 100644
index 000000000000..7f4b5ae24b12
--- /dev/null
+++ b/docs/installation/upgrade_435.md
@@ -0,0 +1,58 @@
+# Upgrading from 4.3.4 to 4.3.5
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Validation Placeholders
+
+To use [Validation Placeholders](../libraries/validation.md#validation-placeholders) securely, please remember to create a validation rule for the field you will use as a placeholder.
+
+E.g., if you have the following code:
+
+```
+$validation->setRules([
+ 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]',
+]);
+```
+
+You need to add the rules for `{id}`:
+
+```
+$validation->setRules([
+ 'id' => 'max_length[19]|is_natural_no_zero', // Add this
+ 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]',
+]);
+```
+
+### Session::stop()
+
+Prior to v4.3.5, the `Session::stop()` method did not destroy the session due to a bug. This method has been modified to destroy the session, and is now deprecated because it is exactly the same as the `Session::destroy()` method. So use the [Session::destroy](../libraries/sessions.md#destroy) method instead.
+
+If you have code that depends on the bug, replace it with `session_regenerate_id(true)`.
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Generators.php**
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Generators.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_436.md b/docs/installation/upgrade_436.md
new file mode 100644
index 000000000000..669b0487ac09
--- /dev/null
+++ b/docs/installation/upgrade_436.md
@@ -0,0 +1,25 @@
+# Upgrading from 4.3.5 to 4.3.6
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+- `AutoRouterInterface::getRoute()` has a new second parameter `string $httpVerb`. If you implement it, add the parameter.
+
+## Breaking Enhancements
+
+- The method signatures of `ValidationInterface::check()` and `Validation::check()` have been changed. If you implement or extend them, update the signatures.
+
+## Project Files
+
+Version 4.3.6 did not alter any executable code in project files.
+
+### 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:
+
+- **composer.json**
diff --git a/docs/installation/upgrade_437.md b/docs/installation/upgrade_437.md
new file mode 100644
index 000000000000..8599ddca8589
--- /dev/null
+++ b/docs/installation/upgrade_437.md
@@ -0,0 +1,71 @@
+# Upgrading from 4.3.6 to 4.3.7
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Feature Testing Request Body
+
+If you call:
+
+1. [withBody()](../testing/feature.md#setting-the-body)
+2. and [withBodyFormat()](../testing/feature.md#formatting-the-request)
+3. and pass the `$params` to [call()](../testing/feature.md#requesting-a-page) (or shorthand methods)
+
+the priority for a Request body has been changed. In the unlikely event that you have test code affected by this change, modify it.
+
+For example, now the `$params` is used to build the request body, and the `$body` is not used:
+
+```
+$this->withBody($body)->withBodyFormat('json')->call('post', $params)
+```
+
+Previously, the `$body` was used for the request body.
+
+### Return value of Validation::loadRuleGroup()
+
+The return value of `Validation::loadRuleGroup()` has been changed from "**rules array**" to "**array** of **rules array** and **customErrors array**" (`[rules, customErrors]`).
+
+If you use the method, update the code like the following:
+
+```
+$rules = $this->validation->loadRuleGroup($rules);
+ ↓
+[$rules, $customErrors] = $this->validation->loadRuleGroup($rules);
+```
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Kint.php**
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Autoload.php**
+- **app/Config/Cache.php**
+- **app/Config/ContentSecurityPolicy.php**
+- **app/Config/Filters.php**
+- **app/Config/Kint.php**
+- **app/Config/Logger.php**
+- **app/Config/Migrations.php**
+- **app/Config/Modules.php**
+- **app/Config/Paths.php**
+- **app/Controllers/BaseController.php**
+- **app/Controllers/Home.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_438.md b/docs/installation/upgrade_438.md
new file mode 100644
index 000000000000..07412099b4dd
--- /dev/null
+++ b/docs/installation/upgrade_438.md
@@ -0,0 +1,27 @@
+# Upgrading from 4.3.7 to 4.3.8
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **composer.json**
+
+### 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:
+
+- **composer.json**
diff --git a/docs/installation/upgrade_440.md b/docs/installation/upgrade_440.md
new file mode 100644
index 000000000000..4be02e75fe30
--- /dev/null
+++ b/docs/installation/upgrade_440.md
@@ -0,0 +1,278 @@
+# Upgrading from 4.3.8 to 4.4.0
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## SECURITY
+
+### When Using $this->validate()
+
+There was a known potential vulnerability in [$this->validate()](../incoming/controllers.md#this-validate) in the Controller to bypass validation. The attack could allow developers to misinterpret unvalidated empty data as validated and proceed with processing.
+
+The [Validation::getValidated()](../libraries/validation.md#getting-validated-data) method has been added to ensure that validated data is obtained.
+
+Therefore, when you use `$this->validate()` in your Controllers, you should use the new `Validation::getValidated()` method to get the validated data.
+
+```php
+// In Controller.
+
+if (! $this->validateData($data, [
+ 'username' => 'required',
+ 'password' => 'required|min_length[10]',
+])) {
+ // The validation failed.
+ return view('login', [
+ 'errors' => $this->validator->getErrors(),
+ ]);
+}
+
+// The validation was successful.
+
+// Get the validated data.
+$validData = $this->validator->getValidated();
+```
+
+## Breaking Changes
+
+### URI::setSegment() Change
+
+Due to a bug, in previous versions an exception was not thrown if the last segment `+2` was specified. This bug has been fixed.
+
+If your code depends on this bug, fix the segment number.
+
+```php
+// URI: http://example.com/one/two
+
+// Before:
+$uri->setSegment(4, 'three');
+// The URI will be http://example.com/one/two/three
+
+// After:
+$uri->setSegment(4, 'three'); // Will throw Exception
+$uri->setSegment(3, 'three');
+// The URI will be http://example.com/one/two/three
+```
+
+### Site URI Changes
+
+- Because of the rework for the current URI determination, the framework may return site URIs or the URI paths slightly differently than in previous versions. It may break your test code. Update assertions if the existing tests fail.
+- When your baseURL has sub-directories and you get the relative path to baseURL of the current URI by the `URI::getPath()` method, you must use the new `SiteURI::getRoutePath()` method instead.
+
+See [Site URI Changes](../changelogs/v4.4.0.md#site-uri-changes) for details.
+
+### When You Extend Exceptions
+
+If you are extending `CodeIgniter\Debug\Exceptions` and have not overridden the `exceptionHandler()` method, defining the new `Config\Exceptions::handler()` method in your **app/Config/Exceptions.php** will cause the specified Exception Handler to be executed.
+
+Your overridden code will no longer be executed, so make any necessary changes by defining your own exception handler.
+
+See [Custom Exception Handlers](../general/errors.md#custom-exception-handlers) for details.
+
+### Auto Routing (Improved) and translateURIDashes
+
+When using Auto Routing (Improved) and `$translateURIDashes` is true (`$routes->setTranslateURIDashes(true)`), in previous versions due to a bug two URIs corresponded to a single controller method, one URI for dashes (e.g., **foo-bar**) and one URI for underscores (e.g., **foo_bar**).
+
+This bug was fixed and now URIs for underscores (**foo_bar**) are not accessible.
+
+If you have links to URIs for underscores (**foo_bar**), update them with URIs for dashes (**foo-bar**).
+
+### When Passing Classname with Namespace to Factories
+
+The behavior of passing a classname with a namespace to Factories has been changed. See [ChangeLog](../changelogs/v4.4.0.md#factories) for details.
+
+If you have code like `model(\Myth\Auth\Models\UserModel::class)` or `model('Myth\Auth\Models\UserModel')` (the code may be in the third-party packages), and you expect to load your `App\Models\UserModel`, you need to define the classname to be loaded before the first loading of that class:
+
+```
+Factories::define('models', 'Myth\Auth\Models\UserModel', 'App\Models\UserModel');
+```
+
+See [Defining Classname to be Loaded](../concepts/factories.md#defining-classname-to-be-loaded) for details.
+
+### Interface Changes
+
+Some interface changes have been made. Classes that implement them should update their APIs to reflect the changes. See [Interface Changes](../changelogs/v4.4.0.md#interface-changes) for details.
+
+### Method Signature Changes
+
+Some method signature changes have been made. Classes that extend them should update their APIs to reflect the changes. See [Method Signature Changes](../changelogs/v4.4.0.md#method-signature-changes) for details.
+
+Also, the parameter types of some constructors and `Services::security()` have changed. If you call them with the parameters, change the parameter values. See [Parameter Type Changes](../changelogs/v4.4.0.md#parameter-type-changes) for details.
+
+### RouteCollection::$routes
+
+The array structure of the protected property `$routes` has been modified for performance.
+
+If you extend `RouteCollection` and use the `$routes`, update your code to match the new array structure.
+
+## Mandatory File Changes
+
+### index.php and spark
+
+The following files received significant changes and **you must merge the updated versions** with your application:
+
+- `public/index.php` (see also [CodeIgniter and exit()](../changelogs/v4.4.0.md#codeigniter-and-exit))
+- `spark`
+
+!!! important "Important"
+ If you don't update the above files, CodeIgniter will not work properly after running `composer update`.
+
+ The upgrade procedure, for example, is as follows:
+
+ ```console
+ composer update
+ cp vendor/codeigniter4/framework/public/index.php public/index.php
+ cp vendor/codeigniter4/framework/spark spark
+ ```
+
+### Config Files
+
+#### app/Config/App.php
+
+The property `$proxyIPs` must be an array. If you don't use proxy servers, it must be `public array $proxyIPs = [];`.
+
+#### app/Config/Routing.php
+
+To clean up the routing system, the following changes were made:
+
+- New **app/Config/Routing.php** file that holds the settings that used to be in the Routes file.
+- The **app/Config/Routes.php** file was simplified so that it only contains the routes without settings and verbiage to clutter the file.
+- The environment-specific routes files are no longer loaded automatically.
+
+So you need to do the following:
+
+1. Copy **app/Config/Routing.php** from the new framework to your **app/Config** directory, and configure it.
+2. Remove all settings in **app/Config/Routes.php** that are no longer needed.
+3. If you use the environment-specific routes files, add them to the `$routeFiles` property in **app/Config/Routing.php**.
+
+#### app/Config/Toolbar.php
+
+You need to add the new properties `$watchedDirectories` and `$watchedExtensions` for [Hot Reloading](../testing/debugging.md#hot-reloading):
+
+```
+--- a/app/Config/Toolbar.php
++++ b/app/Config/Toolbar.php
+@@ -88,4 +88,31 @@ class Toolbar extends BaseConfig
+ * `$maxQueries` defines the maximum amount of queries that will be stored.
+ */
+ public int $maxQueries = 100;
++
++ /**
++ * --------------------------------------------------------------------------
++ * Watched Directories
++ * --------------------------------------------------------------------------
++ *
++ * Contains an array of directories that will be watched for changes and
++ * used to determine if the hot-reload feature should reload the page or not.
++ * We restrict the values to keep performance as high as possible.
++ *
++ * NOTE: The ROOTPATH will be prepended to all values.
++ */
++ public array $watchedDirectories = [
++ 'app',
++ ];
++
++ /**
++ * --------------------------------------------------------------------------
++ * Watched File Extensions
++ * --------------------------------------------------------------------------
++ *
++ * Contains an array of file extensions that will be watched for changes and
++ * used to determine if the hot-reload feature should reload the page or not.
++ */
++ public array $watchedExtensions = [
++ 'php', 'css', 'js', 'html', 'svg', 'json', 'env',
++ ];
+ }
+```
+
+#### app/Config/Events.php
+
+You need to add the code to add a route for [Hot Reloading](../testing/debugging.md#hot-reloading):
+
+```
+--- a/app/Config/Events.php
++++ b/app/Config/Events.php
+@@ -4,6 +4,7 @@ namespace Config;
+
+ use CodeIgniter\Events\Events;
+ use CodeIgniter\Exceptions\FrameworkException;
++use CodeIgniter\HotReloader\HotReloader;
+
+ /*
+ * --------------------------------------------------------------------
+@@ -44,5 +45,11 @@ Events::on('pre_system', static function () {
+ if (CI_DEBUG && ! is_cli()) {
+ Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
+ Services::toolbar()->respond();
++ // Hot Reload route - for framework use on the hot reloader.
++ if (ENVIRONMENT === 'development') {
++ Services::routes()->get('__hot-reload', static function () {
++ (new HotReloader())->run();
++ });
++ }
+ }
+ });
+```
+
+#### app/Config/Cookie.php
+
+The Cookie config items in **app/Config/App.php** are no longer used.
+
+1. Copy **app/Config/Cookie.php** from the new framework to your **app/Config** directory, and configure it.
+2. Remove the properties (from `$cookiePrefix` to `$cookieSameSite`) in **app/Config/App.php**.
+
+#### app/Config/Security.php
+
+The CSRF config items in **app/Config/App.php** are no longer used.
+
+1. Copy **app/Config/Security.php** from the new framework to your **app/Config** directory, and configure it.
+2. Remove the properties (from `$CSRFTokenName` to `$CSRFSameSite`) in **app/Config/App.php**.
+
+#### app/Config/Session.php
+
+The Session config items in **app/Config/App.php** are no longer used.
+
+1. Copy **app/Config/Session.php** from the new framework to your **app/Config** directory, and configure it.
+2. Remove the properties (from `$sessionDriver` to `$sessionDBGroup`) in **app/Config/App.php**.
+
+## Breaking Enhancements
+
+- **Routing:** The method signature of `RouteCollection::__construct()` has been changed. The third parameter `Routing $routing` has been added. Extending classes should likewise add the parameter so as not to break LSP.
+- **Validation:** The method signature of `Validation::check()` has been changed. The `string` typehint on the `$rule` parameter was removed. Extending classes should likewise remove the typehint so as not to break LSP.
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/CURLRequest.php**
+ - The default value of [$shareOptions](../libraries/curlrequest.md#sharing-options) has been changed to `false`.
+- **app/Config/Exceptions.php**
+ - Added the new method `handler()` that defines custom Exception Handlers. See [Custom Exception Handlers](../general/errors.md#custom-exception-handlers).
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/CURLRequest.php**
+- **app/Config/Cookie.php**
+- **app/Config/Database.php**
+- **app/Config/Events.php**
+- **app/Config/Exceptions.php**
+- **app/Config/Filters.php**
+- **app/Config/Routes.php**
+- **app/Config/Routing.php**
+- **app/Config/Toolbar.php**
+- **public/index.php**
+- spark
diff --git a/docs/installation/upgrade_441.md b/docs/installation/upgrade_441.md
new file mode 100644
index 000000000000..01a7059f6d68
--- /dev/null
+++ b/docs/installation/upgrade_441.md
@@ -0,0 +1,29 @@
+# Upgrading from 4.4.0 to 4.4.1
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### Content Changes
+
+Version 4.4.1 did not alter any executable code in project files.
+
+### 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:
+
+- **app/Config/Autoload.php**
+- **app/Config/DocTypes.php**
+- **app/Config/Email.php**
+- **app/Config/ForeignCharacters.php**
+- **app/Config/Mimes.php**
+- **app/Config/Modules.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_442.md b/docs/installation/upgrade_442.md
new file mode 100644
index 000000000000..9f3c848ca081
--- /dev/null
+++ b/docs/installation/upgrade_442.md
@@ -0,0 +1,21 @@
+# Upgrading from 4.4.1 to 4.4.2
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/Migrations.php**
+- **app/Config/View.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_443.md b/docs/installation/upgrade_443.md
new file mode 100644
index 000000000000..050e95032a71
--- /dev/null
+++ b/docs/installation/upgrade_443.md
@@ -0,0 +1,32 @@
+# Upgrading from 4.4.2 to 4.4.3
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Mandatory File Changes
+
+### error_exception.php
+
+The following file received significant changes and **you must merge the updated version** with your application:
+
+- **app/Views/errors/html/error_exception.php**
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/Boot/development.php**
+- **app/Config/Boot/production.php**
+- **app/Config/Boot/testing.php**
+- **app/Config/Filters.php**
+- **app/Views/errors/html/error_404.php**
+- **app/Views/errors/html/error_exception.php**
diff --git a/docs/installation/upgrade_444.md b/docs/installation/upgrade_444.md
new file mode 100644
index 000000000000..b8e1ab21f41d
--- /dev/null
+++ b/docs/installation/upgrade_444.md
@@ -0,0 +1,106 @@
+# Upgrading from 4.4.3 to 4.4.4
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Mandatory File Changes
+
+### Error Files
+
+Update the following files to show correct error messages:
+
+- **app/Views/errors/cli/error_exception.php**
+- **app/Views/errors/html/error_exception.php**
+
+## Breaking Changes
+
+### Validation with Dot Array Syntax
+
+If you are using [dot array syntax](../libraries/validation.md#setting-rules-for-array-data) in validation rules, a bug where `*` would validate data in incorrect dimensions has been fixed.
+
+In previous versions, the rule key `contacts.*.name` captured data with any level like `contacts.*.name`, `contacts.*.*.name`, `contacts.*.*.*.name`, etc., incorrectly.
+
+The following code explains the details:
+
+```php
+use Config\Services;
+
+$validation = Services::validation();
+
+$data = [
+ 'contacts' => [
+ 'name' => 'Joe Smith',
+ 'just' => [
+ 'friends' => [
+ ['name' => 'SATO Taro'],
+ ['name' => 'Li Ming'],
+ ['name' => 'Heinz Müller'],
+ ],
+ ],
+ ],
+];
+
+$validation->setRules(
+ ['contacts.*.name' => 'required|max_length[8]'],
+);
+
+$validation->run($data); // false
+
+d($validation->getErrors());
+/*
+ Before: Captured `contacts.*.*.*.name` incorrectly.
+ [
+ contacts.just.friends.0.name => "The contacts.*.name field cannot exceed 8 characters in length.",
+ contacts.just.friends.2.name => "The contacts.*.name field cannot exceed 8 characters in length.",
+ ]
+
+ After: Captures no data for `contacts.*.name`.
+ [
+ contacts.*.name => string (38) "The contacts.*.name field is required.",
+ ]
+*/
+```
+
+If you have code that depends on the bug, fix the rule key.
+
+### Validation rules matches and differs
+
+Because bugs have been fixed in the case where `matches` and `differs` in the Strict and Traditional rules validate data of non-string types, if you are using these rules and validate non-string data, the validation results might be changed (fixed).
+
+Note that Traditional Rules should not be used to validate data that is not a string.
+
+### The use of the `ssl_key` option in CURLRequest was removed
+
+The CURLRequest option `ssl_key` is not recognized anymore. If in use, the option `ssl_key` must be replaced with the option `verify` in order to define the path to a CA bundle for CURLRequest.
+
+The CURLRequest option `verify` can also take *boolean* values as usual.
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Autoload.php**
+- **app/Config/Boot/development.php**
+- **app/Config/Boot/testing.php**
+- **app/Config/Cache.php**
+- **app/Config/Email.php**
+- **app/Config/Filters.php**
+- **app/Config/Kint.php**
+- **app/Config/Modules.php**
+- **app/Config/Publisher.php**
+- **app/Config/Session.php**
+- **app/Views/errors/cli/error_exception.php**
+- **app/Views/errors/html/error_exception.php**
+- **composer.json**
+- env
+- spark
diff --git a/docs/installation/upgrade_445.md b/docs/installation/upgrade_445.md
new file mode 100644
index 000000000000..ecd59b125f0f
--- /dev/null
+++ b/docs/installation/upgrade_445.md
@@ -0,0 +1,19 @@
+# Upgrading from 4.4.4 to 4.4.5
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **composer.json**
diff --git a/docs/installation/upgrade_446.md b/docs/installation/upgrade_446.md
new file mode 100644
index 000000000000..dc70c341d8dd
--- /dev/null
+++ b/docs/installation/upgrade_446.md
@@ -0,0 +1,36 @@
+# Upgrading from 4.4.5 to 4.4.6
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Time::createFromTimestamp() Timezone Change
+
+When you do not specify a timezone, now [Time::createFromTimestamp()](../libraries/time.md#createfromtimestamp) returns a Time instance with the app's timezone.
+
+If you want to keep the timezone UTC, you need to call `setTimezone('UTC')`:
+
+```
+use CodeIgniter\I18n\Time;
+
+$time = Time::createFromTimestamp(1501821586)->setTimezone('UTC');
+```
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Routing.php**
+- **app/Views/welcome_message.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_447.md b/docs/installation/upgrade_447.md
new file mode 100644
index 000000000000..d409534fcf96
--- /dev/null
+++ b/docs/installation/upgrade_447.md
@@ -0,0 +1,106 @@
+# Upgrading from 4.4.6 to 4.4.7
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Mandatory File Changes
+
+### URI Security
+
+A feature to check that URIs do not contain disallowed strings has been added. This check is equivalent to the URI Security found in CodeIgniter 3.
+
+We recommend you enable this feature. Add the following to **app/Config/App.php**:
+
+```
+public string $permittedURIChars = 'a-z 0-9~%.:_\-';
+```
+
+See [URI Security](../general/urls.md#uri-security) for details.
+
+### Error Files
+
+The error page has been updated. Please update the following files:
+
+- **app/Views/errors/html/debug.css**
+- **app/Views/errors/html/error_exception.php**
+
+## Breaking Changes
+
+### Paths in Controller Filters
+
+A bug where URI paths processed by [Controller Filters](../incoming/filters.md) were not URL-decoded has been fixed.
+
+!!! note "Note"
+ [Router](../incoming/routing.md) processes URL-decoded URI paths.
+
+`Config\Filters` has some places to specify the URI paths. If the paths have different values when URL-decoded, change them to the URL-decoded values.
+
+For example:
+
+```php
+public array $globals = [
+ 'before' => [
+ 'csrf' => ['except' => '%E6%97%A5%E6%9C%AC%E8%AA%9E/*'],
+ ],
+ // ...
+];
+```
+
+↓
+
+```php
+public array $globals = [
+ 'before' => [
+ 'csrf' => ['except' => '日本語/*'],
+ ],
+ // ...
+];
+```
+
+### Time::difference() and DST
+
+In previous versions, when comparing dates with `Time::difference()`, unexpected results were returned if the date included a day different from 24 hours due to Daylight Saving Time (DST). See [Note in Times and Dates](../libraries/time.md#viewing-differences) for details.
+
+This bug has been fixed, so date comparisons will now be shifted by one day in such cases.
+
+In the unlikely event that you wish to maintain the behavior of the previous versions, change the time zone of both dates being compared to UTC before passing them to `Time::difference()`.
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/App.php**
+ - The property `$permittedURIChars` was added. See [URI Security](../general/urls.md#uri-security) for details.
+
+### 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:
+
+- **app/Config/App.php**
+- **app/Config/Cache.php**
+- **app/Config/ContentSecurityPolicy.php**
+- **app/Config/Database.php**
+- **app/Config/Exceptions.php**
+- **app/Config/Filters.php**
+- **app/Config/Format.php**
+- **app/Config/Logger.php**
+- **app/Config/Mimes.php**
+- **app/Config/Routing.php**
+- **app/Config/Toolbar.php**
+- **app/Config/Validation.php**
+- **app/Config/View.php**
+- **app/Controllers/BaseController.php**
+- **app/Views/errors/html/debug.css**
+- **app/Views/errors/html/error_exception.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_448.md b/docs/installation/upgrade_448.md
new file mode 100644
index 000000000000..a4a8aa112db7
--- /dev/null
+++ b/docs/installation/upgrade_448.md
@@ -0,0 +1,21 @@
+# Upgrading from 4.4.7 to 4.4.8
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/.htaccess**
+- **public/.htaccess**
+- **writable/.htaccess**
diff --git a/docs/installation/upgrade_450.md b/docs/installation/upgrade_450.md
new file mode 100644
index 000000000000..f64323a7ffe8
--- /dev/null
+++ b/docs/installation/upgrade_450.md
@@ -0,0 +1,305 @@
+# Upgrading from 4.4.8 to 4.5.0
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Mandatory File Changes
+
+### index.php and spark
+
+The following files received significant changes and **you must merge the updated versions** with your application:
+
+- `public/index.php`
+- `spark`
+
+!!! important "Important"
+ If you don't update the above files, CodeIgniter will not work properly after running `composer update`.
+
+ The upgrade procedure, for example, is as follows:
+
+ ```console
+ composer update
+ cp vendor/codeigniter4/framework/public/index.php public/index.php
+ cp vendor/codeigniter4/framework/spark spark
+ ```
+
+## Breaking Changes
+
+### Lowercase HTTP Method Name
+
+#### Request::getMethod()
+
+For historical reasons, `Request::getMethod()` returned HTTP method names in lower case by default.
+
+But the method token is case-sensitive because it might be used as a gateway to object-based systems with case-sensitive method names. By convention, standardized methods are defined in all-uppercase US-ASCII letters. See .
+
+Now the deprecated `$upper` parameter in `Request::getMethod()` has been removed, and `getMethod()` returns the HTTP method name as-is. That is, uppercase like "GET", "POST", and so on.
+
+If you want lowercase HTTP method names, use PHP's `strtolower()` function:
+
+```
+strtolower($request->getMethod())
+```
+
+And you should use uppercase HTTP method names in your app code.
+
+#### app/Config/Filters.php
+
+You should update the keys to uppercase in `$methods` in **app/Config/Filters.php**:
+
+```
+public array $methods = [
+ 'POST' => ['invalidchars', 'csrf'],
+ 'GET' => ['csrf'],
+];
+```
+
+#### CURLRequest::request()
+
+In previous versions, you could pass lowercase HTTP methods to the `request()` method. But this bug has been fixed.
+
+Now you must pass the correct HTTP method names like `GET`, `POST`. Otherwise, you will get an error response:
+
+```
+$client = \Config\Services::curlrequest();
+$response = $client->request('get', 'https://www.google.com/', [
+ 'http_errors' => false,
+]);
+$response->getStatusCode(); // In previous versions: 200
+ // In this version: 405
+```
+
+### Nested Route Groups and Options
+
+A bug that prevented options passed to outer `group()` from being merged with options in inner `group()` has been fixed.
+
+Check and correct your route configuration as it could change the values of the options applied.
+
+For example,
+
+```php
+$routes->group('admin', ['filter' => 'csrf'], static function ($routes) {
+ $routes->get('/', static function () {
+ // ...
+ });
+
+ $routes->group('users', ['namespace' => 'Users'], static function ($routes) {
+ $routes->get('/', static function () {
+ // ...
+ });
+ });
+});
+```
+
+Now the `csrf` filter is executed for both the route `admin` and `admin/users`. In previous versions, it was executed only for the route `admin`. See also [Nesting Groups](../incoming/routing.md#nesting-groups).
+
+### Filter Execution Order
+
+The order in which Controller Filters are executed has changed. If you wish to maintain the same execution order as in previous versions, set `Config\Feature::$oldFilterOrder` to `true`. See also [Filter Execution Order](../incoming/filters.md#filter-execution-order).
+
+1. The order of execution of filter groups has been changed.
+
+ Before Filters:
+
+ ```
+ Previous: route → globals → methods → filters
+ Now: globals → methods → filters → route
+ ```
+
+ After Filters:
+
+ ```
+ Previous: route → globals → filters
+ Now: route → filters → globals
+ ```
+
+2. The After Filters in *Route* filters and *Filters* filters execution order is now reversed.
+
+ When you have the following configuration:
+
+ ```php
+ // In app/Config/Routes.php
+ $routes->get('/', 'Home::index', ['filter' => ['route1', 'route2']]);
+
+ // In app/Config/Filters.php
+ public array $filters = [
+ 'filter1' => ['before' => '*', 'after' => '*'],
+ 'filter2' => ['before' => '*', 'after' => '*'],
+ ];
+ ```
+
+ Before Filters:
+
+ ```
+ Previous: route1 → route2 → filter1 → filter2
+ Now: filter1 → filter2 → route1 → route2
+ ```
+
+ After Filters:
+
+ ```
+ Previous: route1 → route2 → filter1 → filter2
+ Now: route2 → route1 → filter2 → filter1
+ ```
+
+### API\ResponseTrait and String Data
+
+In previous versions, if you passed string data to a trait method, the framework returned an HTML response, even if the response format was determined to be JSON.
+
+Now if you pass string data, it returns a JSON response correctly. See also [Handling Response Types](../outgoing/api_responses.md#handling-response-types).
+
+You can keep the behavior in previous versions if you set the `$stringAsHtml` property to `true` in your controller.
+
+### FileLocator::findQualifiedNameFromPath()
+
+In previous versions, `FileLocator::findQualifiedNameFromPath()` returned Fully Qualified Classnames with a leading `\`. Now the leading `\` has been removed.
+
+If you have code that expects a leading `\`, fix it.
+
+### BaseModel::getIdValue()
+
+The `BaseModel::getIdValue()` method has been changed to `abstract`, and the implementation has been removed.
+
+If you extend `BaseModel`, implement the `getIdValue()` method in the child class.
+
+### Factories
+
+[Factories](../concepts/factories.md) has been changed to a final class. In the unlikely event that you have inherited Factories, stop inheriting and copy the code into your Factories class.
+
+### Auto Routing (Legacy)
+
+In previous versions, the controller filters might be executed even when the corresponding controller was not found.
+
+This bug has been fixed and now a `PageNotFoundException` will be thrown and the filters will not be executed if the controller is not found.
+
+If you have code that depends on this bug, for example if you expect global filters to be executed even for non-existent pages, use the new [Required Filters](../changelogs/v4.5.0.md#required-filters).
+
+### Method Signature Changes
+
+Some method signature changes have been made. Classes that extend them should update their APIs to reflect the changes. See [ChangeLog](../changelogs/v4.5.0.md#method-signature-changes) for details.
+
+### Removed Deprecated Items
+
+Some deprecated items have been removed. If you are still using these items, or extending these classes, upgrade your code. See [ChangeLog](../changelogs/v4.5.0.md#removed-deprecated-items) for details.
+
+## Breaking Enhancements
+
+### 404 Override Status Code
+
+In previous versions, [404 Override](../incoming/routing.md#404-override) returned responses with the status code `200` by default. Now it returns `404` by default.
+
+If you want `200`, you need to set it in the controller:
+
+```
+$routes->set404Override(static function () {
+ response()->setStatusCode(200);
+
+ echo view('my_errors/not_found.html');
+});
+```
+
+### Validation::run() Signature
+
+The method signatures of `Validation::run()` and `ValidationInterface::run()` have been changed. The `?string` typehint on the `$dbGroup` parameter was removed. Extending classes should likewise remove the typehint so as not to break LSP.
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+##### app/Config/Filters.php
+
+Required Filters have been added, so the following changes were made. See also [ChangeLog](../changelogs/v4.5.0.md#required-filters).
+
+The base class has been changed:
+
+```
+class Filters extends \CodeIgniter\Config\Filters
+```
+
+The following items are added in the `$aliases` property:
+
+```
+public array $aliases = [
+ // ...
+ 'forcehttps' => \CodeIgniter\Filters\ForceHTTPS::class,
+ 'pagecache' => \CodeIgniter\Filters\PageCache::class,
+ 'performance' => \CodeIgniter\Filters\PerformanceMetrics::class,
+];
+```
+
+A new property `$required` is added, and set as follows:
+
+```
+public array $required = [
+ 'before' => [
+ 'forcehttps', // Force Global Secure Requests
+ 'pagecache', // Web Page Caching
+ ],
+ 'after' => [
+ 'pagecache', // Web Page Caching
+ 'performance', // Performance Metrics
+ 'toolbar', // Debug Toolbar
+ ],
+];
+```
+
+The `'toolbar'` in the `$globals['after']` was removed.
+
+##### Others
+
+- **app/Config/Boot/production.php**
+ - The default error level to `error_reporting()` has been changed to `E_ALL & ~E_DEPRECATED`.
+- **app/Config/Cors.php**
+ - Added to handle CORS configuration.
+- **app/Config/Database.php**
+ - The default value of `charset` in `$default` has been changed to `utf8mb4`.
+ - The default value of `DBCollat` in `$default` has been changed to `utf8mb4_general_ci`.
+ - The default value of `DBCollat` in `$tests` has been changed to `''`.
+- **app/Config/Feature.php**
+ - `Config\Feature::$oldFilterOrder` has been added. See [Filter Execution Order](../incoming/filters.md#filter-execution-order).
+ - `Config\Feature::$limitZeroAsAll` has been added. See [limit(0) Behavior](../changelogs/v4.5.0.md#limit0-behavior).
+ - `Config\Feature::$multipleFilters` has been removed, because now [Multiple Filters](../incoming/routing.md#multiple-filters) are always enabled.
+- **app/Config/Kint.php**
+ - It no longer extends `BaseConfig` because enabling [Config Caching](../concepts/factories.md#config-caching) could cause errors.
+- **app/Config/Optimize.php**
+ - Added to handle optimization configuration.
+- **app/Config/Security.php**
+ - The property `$redirect` has been changed to `true` in the `production` environment.
+
+### 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:
+
+- **app/Config/Autoload.php**
+- **app/Config/Boot/production.php**
+- **app/Config/Cache.php**
+- **app/Config/Cors.php**
+- **app/Config/Database.php**
+- **app/Config/Feature.php**
+- **app/Config/Filters.php**
+- **app/Config/Generators.php**
+- **app/Config/Kint.php**
+- **app/Config/Optimize.php**
+- **app/Config/Routing.php**
+- **app/Config/Security.php**
+- **app/Config/Session.php**
+- **app/Views/errors/cli/error_exception.php**
+- **app/Views/errors/html/error_exception.php**
+- **app/Views/welcome_message.php**
+- **composer.json**
+- env
+- **phpunit.xml.dist**
+- **preload.php**
+- **public/index.php**
+- spark
diff --git a/docs/installation/upgrade_451.md b/docs/installation/upgrade_451.md
new file mode 100644
index 000000000000..d040b136864a
--- /dev/null
+++ b/docs/installation/upgrade_451.md
@@ -0,0 +1,26 @@
+# Upgrading from 4.5.0 to 4.5.1
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- .gitignore
+- **composer.json**
+- **phpunit.xml.dist**
+- **tests/.htaccess**
+- **tests/index.html**
+- **writable/debugbar/.gitkeep** (Removed)
+- **writable/debugbar/index.html**
+- **writable/index.html**
diff --git a/docs/installation/upgrade_452.md b/docs/installation/upgrade_452.md
new file mode 100644
index 000000000000..8143c8dc452c
--- /dev/null
+++ b/docs/installation/upgrade_452.md
@@ -0,0 +1,29 @@
+# Upgrading from 4.5.1 to 4.5.2
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/DocTypes.php**
+- **app/Config/Exceptions.php**
+- **preload.php**
+- spark
+- **writable/.htaccess**
+- **writable/cache/index.html**
+- **writable/debugbar/index.html**
+- **writable/index.html**
+- **writable/logs/index.html**
+- **writable/session/index.html**
diff --git a/docs/installation/upgrade_453.md b/docs/installation/upgrade_453.md
new file mode 100644
index 000000000000..997bfef02a1a
--- /dev/null
+++ b/docs/installation/upgrade_453.md
@@ -0,0 +1,28 @@
+# Upgrading from 4.5.2 to 4.5.3
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **composer.json**
+
+### 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:
+
+- **composer.json**
diff --git a/docs/installation/upgrade_454.md b/docs/installation/upgrade_454.md
new file mode 100644
index 000000000000..97a338c29e62
--- /dev/null
+++ b/docs/installation/upgrade_454.md
@@ -0,0 +1,21 @@
+# Upgrading from 4.5.3 to 4.5.4
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/Events.php**
+- **composer.json**
diff --git a/docs/installation/upgrade_455.md b/docs/installation/upgrade_455.md
new file mode 100644
index 000000000000..d4f6cb5bab69
--- /dev/null
+++ b/docs/installation/upgrade_455.md
@@ -0,0 +1,28 @@
+# Upgrading from 4.5.4 to 4.5.5
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **preload.php**
+
+### 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:
+
+- **preload.php**
diff --git a/docs/installation/upgrade_456.md b/docs/installation/upgrade_456.md
new file mode 100644
index 000000000000..28da23e951c0
--- /dev/null
+++ b/docs/installation/upgrade_456.md
@@ -0,0 +1,24 @@
+# Upgrading from 4.5.5 to 4.5.6
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Config/Events.php**
+- **app/Config/Format.php**
+- **app/Controllers/BaseController.php**
+- **app/Views/errors/cli/error_exception.php**
+- **app/Views/errors/html/error_exception.php**
diff --git a/docs/installation/upgrade_457.md b/docs/installation/upgrade_457.md
new file mode 100644
index 000000000000..2eca889708a6
--- /dev/null
+++ b/docs/installation/upgrade_457.md
@@ -0,0 +1,20 @@
+# Upgrading from 4.5.6 to 4.5.7
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+- **app/Views/errors/cli/error_exception.php**
diff --git a/docs/installation/upgrade_458.md b/docs/installation/upgrade_458.md
new file mode 100644
index 000000000000..50237d54ad7c
--- /dev/null
+++ b/docs/installation/upgrade_458.md
@@ -0,0 +1,26 @@
+# Upgrading from 4.5.7 to 4.5.8
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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:
+
+### 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:
+
+- **preload.php**
+- **public/index.php**
+- spark
diff --git a/docs/installation/upgrade_460.md b/docs/installation/upgrade_460.md
new file mode 100644
index 000000000000..418b0bff7c76
--- /dev/null
+++ b/docs/installation/upgrade_460.md
@@ -0,0 +1,280 @@
+# Upgrading from 4.5.8 to 4.6.0
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### Exception Changes
+
+Some classes have changed the exception classes that are thrown. Some exception classes have changed parent classes. See [ChangeLog](../changelogs/v4.6.0.md#exceptions) for details.
+
+If you have code that catches these exceptions, change the exception classes.
+
+### Time::createFromTimestamp() Timezone Change
+
+When you do not explicitly pass a timezone, now [Time::createFromTimestamp()](../libraries/time.md#createfromtimestamp) returns a Time instance with **UTC**. From v4.4.6 to before v4.6.0, a Time instance with the currently set default timezone was returned.
+
+This behavior change normalizes behavior with changes in PHP 8.4, which adds a new `DateTimeInterface::createFromTimestamp()` method.
+
+If you want to keep the default timezone, you need to pass the timezone as the second parameter:
+
+```
+use CodeIgniter\I18n\Time;
+
+$time = Time::createFromTimestamp(1501821586, date_default_timezone_get());
+```
+
+### Time keeps Microseconds
+
+In previous versions, [Time](../libraries/time.md) lost microseconds in some cases. But the bugs have been fixed.
+
+The results of the `Time` comparison may differ due to these fixes:
+
+```php
+use CodeIgniter\I18n\Time;
+
+$time1 = new Time('2024-01-01 12:00:00.654321');
+$time2 = new Time('2024-01-01 12:00:00');
+
+$time1->equals($time2);
+// Before: true
+// After: false
+```
+
+In such a case, you need to remove the microseconds:
+
+```php
+use CodeIgniter\I18n\Time;
+
+$time1 = new Time('2024-01-01 12:00:00.654321');
+$time2 = new Time('2024-01-01 12:00:00');
+
+// Removes the microseconds.
+$time1 = Time::createFromFormat(
+ 'Y-m-d H:i:s',
+ $time1->format('Y-m-d H:i:s'),
+ $time1->getTimezone(),
+);
+
+$time1->equals($time2);
+// Before: true
+// After: true
+```
+
+The following cases now keep microseconds:
+
+```php
+use CodeIgniter\I18n\Time;
+
+$time = Time::createFromFormat('Y-m-d H:i:s.u', '2024-07-09 09:13:34.654321');
+echo $time->format('Y-m-d H:i:s.u');
+// Before: 2024-07-09 09:13:34.000000
+// After: 2024-07-09 09:13:34.654321
+```
+
+```php
+use CodeIgniter\I18n\Time;
+
+$time = new Time('1 hour ago');
+echo $time->format('Y-m-d H:i:s.u');
+// Before: 2024-07-26 21:05:57.000000
+// After: 2024-07-26 21:05:57.857235
+```
+
+Note that `Time` with the current time already held microseconds before this change.
+
+```php
+use CodeIgniter\I18n\Time;
+
+$time = Time::now();
+echo $time->format('Y-m-d H:i:s.u');
+// Before: 2024-07-26 21:39:32.249072
+// After: 2024-07-26 21:39:32.249072
+```
+
+Also, methods that return an `int` still lose the microseconds.
+
+```php
+use CodeIgniter\I18n\Time;
+
+$time1 = new Time('2024-01-01 12:00:00');
+echo $time1->getTimestamp(); // 1704110400
+
+$time2 = new Time('2024-01-01 12:00:00.654321');
+echo $time2->getTimestamp(); // 1704110400
+```
+
+### Time::setTimestamp() Behavior Fix
+
+In previous versions, calling `Time::setTimestamp()` on a Time instance with a timezone other than the default timezone might return a Time instance with the wrong date/time.
+
+This bug has been fixed, and it now behaves in the same way as `DateTimeImmutable`:
+
+```php
+use CodeIgniter\I18n\Time;
+
+// The Application Timezone is "UTC".
+
+// Set $time1 timezone to "America/Chicago".
+$time1 = Time::parse('2024-08-20', 'America/Chicago');
+
+// The timestamp is "2024-08-20 00:00" in "UTC".
+$stamp = strtotime('2024-08-20'); // 1724112000
+
+// But $time2 timezone is "America/Chicago".
+$time2 = $time1->setTimestamp($stamp);
+
+echo $time2->format('Y-m-d H:i:s P');
+// Before: 2024-08-20 00:00:00 -05:00
+// After: 2024-08-19 19:00:00 -05:00
+```
+
+Note that if you use the default timezone, the behavior is not changed:
+
+```php
+use CodeIgniter\I18n\Time;
+
+// The Application Timezone is "America/Chicago".
+
+// $time1 timezone is "America/Chicago".
+$time1 = Time::parse('2024-08-20');
+
+// The timestamp is "2024-08-20 00:00" in "America/Chicago".
+$stamp = strtotime('2024-08-20'); // 1724130000
+
+// $time2 timezone is also "America/Chicago".
+$time2 = $time1->setTimestamp($stamp);
+
+echo $time2->format('Y-m-d H:i:s P');
+// Before: 2024-08-20 00:00:00 -05:00
+// After: 2024-08-20 00:00:00 -05:00
+```
+
+### Registrars with Dirty Hack
+
+To prevent Auto-Discovery of [Registrars](../general/configuration.md#registrars) from running twice, when a registrar class is loaded or instantiated, if it instantiates a Config class (which extends `CodeIgniter\Config\BaseConfig`), `ConfigException` will be raised.
+
+This is because if Auto-Discovery of Registrars is performed twice, duplicate values may be added to properties of Config classes.
+
+All registrar classes (**Config/Registrar.php** in all namespaces) must be modified so that they do not instantiate any Config class when loaded or instantiated.
+
+If the packages/modules you are using provide such registrar classes, the registrar classes in the packages/modules need to be fixed.
+
+The following is an example of code that will no longer work:
+
+```php
+ [
+ 'module_pager' => 'MyModule\Views\Pager',
+ ],
+ ];
+ }
+
+ public static function hack(): void
+ {
+ $config = config('Cache');
+
+ // Does something.
+ }
+}
+
+Registrar::hack(); // Bad. When this class is loaded, Config\Cache will be instantiated.
+```
+
+### Session ID (SID) Change
+
+Now [Session Library](../libraries/sessions.md) forces the use of the PHP default 32-character SIDs, with 4 bits of entropy per character. This change is to match the behavior of PHP 9.
+
+In other words, the following settings are always used:
+
+```ini
+session.sid_bits_per_character = 4
+session.sid_length = 32
+```
+
+In previous versions, the PHP ini settings were respected. So this change may change your SID length.
+
+If you cannot accept this change, customize the Session library.
+
+### Interface Changes
+
+Some interface changes have been made. Classes that implement them should update their APIs to reflect the changes. See [ChangeLog](../changelogs/v4.6.0.md#headers) for details.
+
+### Method Signature Changes
+
+Some method signature changes have been made. Classes that extend them should update their APIs to reflect the changes. See [ChangeLog](../changelogs/v4.6.0.md#method-signature-changes) for details.
+
+### Removed Deprecated Items
+
+Some deprecated items have been removed. If you are still using these items, or extending these classes, upgrade your code. See [ChangeLog](../changelogs/v4.6.0.md#removed-deprecated-items) for details.
+
+## Breaking Enhancements
+
+### Filters Changes
+
+The `Filters` class has been changed to allow multiple runs of the same filter with different arguments in before or after.
+
+If you are extending `Filters`, you will need to modify it to conform to the following changes:
+
+- The structure of the array properties `$filters` and `$filtersClasses` has been changed.
+- The properties `$arguments` and `$argumentsClass` are no longer used.
+- `Filters` has been changed so that the same filter class is not instantiated multiple times. If a filter class is used both before and after, the same instance is used.
+
+## Project Files
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to these files being outside of the **system** scope, they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Feature.php**
+ - `Config\Feature::$autoRoutesImproved` has been changed to `true`.
+ - `Config\Feature::$strictLocaleNegotiation` has been added.
+- **app/Config/Routing.php**
+ - `Config\Routing::$translateUriToCamelCase` has been changed to `true`.
+- **app/Config/Kint.php**
+ - `Config\Kint::$richSort` has been removed. Kint in v6 no longer uses `AbstractRenderer::SORT_FULL`. Leaving this property in your code will cause a runtime error due to the undefined constant.
+
+### 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:
+
+- **app/Config/Cache.php**
+- **app/Config/Constants.php**
+- **app/Config/Database.php**
+- **app/Config/Feature.php**
+- **app/Config/Format.php**
+- **app/Config/Kint.php**
+- **app/Config/Routing.php**
+- **app/Config/Security.php**
+- **app/Views/errors/html/debug.css**
+- **app/Views/errors/html/error_400.php**
+- **preload.php**
+- **public/index.php**
+- spark
diff --git a/docs/installation/upgrade_461.md b/docs/installation/upgrade_461.md
new file mode 100644
index 000000000000..d1bb75934cb3
--- /dev/null
+++ b/docs/installation/upgrade_461.md
@@ -0,0 +1,39 @@
+# Upgrading from 4.6.0 to 4.6.1
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Mimes.php**
+ - `Config\Mimes::$mimes` has been updated to include the `application/octet-stream` and `model/stl` MIME types for the `.stl` file extension.
+
+### 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:
+
+- **app/Config/Autoload.php**
+- **app/Config/Cache.php**
+- **app/Config/DocTypes.php**
+- **app/Config/Mimes.php**
+- **app/Config/Modules.php**
+- **app/Config/Optimize.php**
+- **app/Config/Paths.php**
+- **app/Views/errors/html/debug.css**
+- **preload.php**
+- **public/index.php**
+- spark
diff --git a/docs/installation/upgrade_462.md b/docs/installation/upgrade_462.md
new file mode 100644
index 000000000000..1316045af69d
--- /dev/null
+++ b/docs/installation/upgrade_462.md
@@ -0,0 +1,49 @@
+# Upgrading from 4.6.1 to 4.6.2
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Autoload.php**
+- **app/Config/Cache.php**
+- **app/Config/Cookie.php**
+- **app/Config/DocTypes.php**
+- **app/Config/Logger.php**
+- **app/Config/Mimes.php**
+- **app/Config/Modules.php**
+- **app/Config/Optimize.php**
+- **app/Config/Paths.php**
+
+### 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:
+
+- **app/Config/Autoload.php**
+- **app/Config/Cache.php**
+- **app/Config/Cookie.php**
+- **app/Config/DocTypes.php**
+- **app/Config/Logger.php**
+- **app/Config/Mimes.php**
+- **app/Config/Modules.php**
+- **app/Config/Optimize.php**
+- **app/Config/Paths.php**
+- **app/Views/errors/html/debug.css**
+- **app/Views/errors/html/error_exception.php**
+- **preload.php**
+- **public/index.php**
+- spark
diff --git a/docs/installation/upgrade_463.md b/docs/installation/upgrade_463.md
new file mode 100644
index 000000000000..103787e57e80
--- /dev/null
+++ b/docs/installation/upgrade_463.md
@@ -0,0 +1,29 @@
+# Upgrading from 4.6.2 to 4.6.3
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Filters.php**
+
+### 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:
+
+- **app/Config/Filters.php**
+- **preload.php**
diff --git a/docs/installation/upgrade_464.md b/docs/installation/upgrade_464.md
new file mode 100644
index 000000000000..0ac7fcd55835
--- /dev/null
+++ b/docs/installation/upgrade_464.md
@@ -0,0 +1,31 @@
+# Upgrading from 4.6.3 to 4.6.4
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Database.php**
+ - Added the `synchronous` key for `Config\Database::$tests`.
+
+### 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:
+
+- **app/Config/Database.php**
+- **app/Controllers/BaseController.php**
+- **preload.php**
diff --git a/docs/installation/upgrade_465.md b/docs/installation/upgrade_465.md
new file mode 100644
index 000000000000..af4315cfd3d6
--- /dev/null
+++ b/docs/installation/upgrade_465.md
@@ -0,0 +1,28 @@
+# Upgrading from 4.6.4 to 4.6.5
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- No config files were changed in this release.
+
+### 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:
+
+- No project files were changed in this release.
diff --git a/docs/installation/upgrade_470.md b/docs/installation/upgrade_470.md
new file mode 100644
index 000000000000..88e8119e8a20
--- /dev/null
+++ b/docs/installation/upgrade_470.md
@@ -0,0 +1,116 @@
+# Upgrading from 4.6.x to 4.7.0
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### PHP 8.2 Required
+
+The minimum PHP requirement has been updated to **PHP 8.2**.
+
+If your current runtime is older than PHP 8.2, upgrade PHP first before upgrading CodeIgniter.
+
+### Validation `regex_match` Placeholders
+
+Placeholders in the `regex_match` validation rule must now use double curly braces.
+
+If you previously used single braces like `regex_match[/^{placeholder}$/]`, update it to: `regex_match[/^{{placeholder}}$/]`.
+
+This avoids ambiguity with regex quantifiers such as `{1,3}`.
+
+### Model Primary Key Validation Timing and Exceptions
+
+The `insertBatch()` and `updateBatch()` methods now honor model settings like `updateOnlyChanged` and `allowEmptyInserts`. This change ensures consistent handling across all insert/update operations.
+
+Primary key values are now validated before database queries in `insert()`/`insertBatch()` (without auto-increment), `update()`, and `delete()`.
+
+Invalid primary key values now throw `InvalidArgumentException` instead of database-layer `DatabaseException`.
+
+If your code catches `DatabaseException` for invalid primary keys, update it to handle `InvalidArgumentException` as well.
+
+### Entity Change Detection Is Now Deep
+
+`Entity::hasChanged()` and `Entity::syncOriginal()` now perform deep comparison for arrays and objects.
+
+If you relied on the previous shallow (reference-based) behavior, review your entity update flows and tests because nested changes are now detected.
+
+Also, `Entity::toRawArray()` now recursively converts arrays of entities when `$recursive` is `true`.
+
+### Encryption Handler Key State
+
+`OpenSSLHandler` and `SodiumHandler` no longer mutate the handler's internal key when a key is passed via `$params` to `encrypt()`/`decrypt()`.
+
+If your code depended on passing a key once and reusing it implicitly later, move to explicit key configuration in `Config\Encryption` (or pass a custom config when creating the encrypter service).
+
+### Interface Changes
+
+Some interface changes have been made. Classes that implement framework interfaces should update their APIs to reflect these changes.
+
+See [ChangeLog](../changelogs/v4.7.0.md#interface-changes) for details.
+
+### Method Signature Changes
+
+Some method signature changes have been made. Classes that extend framework classes should update their method signatures to keep LSP compatibility.
+
+See [ChangeLog](../changelogs/v4.7.0.md#method-signature-changes) for details.
+
+### Property Signature Changes
+
+Some property type signatures have changed (for example nullable `Entity::$dataCaster`). If you extend these classes, update your code accordingly.
+
+See [ChangeLog](../changelogs/v4.7.0.md#property-signature-changes) for details.
+
+### Removed Deprecated Items
+
+Some deprecated items have been removed. If your app still uses or extends these APIs, update your code before upgrading.
+
+See [ChangeLog](../changelogs/v4.7.0.md#removed-deprecated-items) for details.
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Migrations.php**
+ - `Config\Migrations::$lock` has been added, with a default value set to `false`.
+
+These files are new in this release:
+
+- **app/Config/Hostnames.php**
+- **app/Config/WorkerMode.php**
+
+### 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:
+
+- **app/Config/CURLRequest.php**
+- **app/Config/Cache.php**
+- **app/Config/ContentSecurityPolicy.php**
+- **app/Config/Email.php**
+- **app/Config/Encryption.php**
+- **app/Config/Format.php**
+- **app/Config/Hostnames.php**
+- **app/Config/Images.php**
+- **app/Config/Migrations.php**
+- **app/Config/Optimize.php**
+- **app/Config/Paths.php**
+- **app/Config/Routing.php**
+- **app/Config/Session.php**
+- **app/Config/Toolbar.php**
+- **app/Config/UserAgents.php**
+- **app/Config/View.php**
+- **app/Config/WorkerMode.php**
+- **public/index.php**
+- spark
diff --git a/docs/installation/upgrade_471.md b/docs/installation/upgrade_471.md
new file mode 100644
index 000000000000..c37745246699
--- /dev/null
+++ b/docs/installation/upgrade_471.md
@@ -0,0 +1,48 @@
+# Upgrading from 4.7.0 to 4.7.1
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Mandatory File Changes
+
+### Worker Mode
+
+If you are using Worker Mode, you must update **public/frankenphp-worker.php** after upgrading. The easiest way is to re-run the install command:
+
+```console
+php spark worker:install --force
+```
+
+## Breaking Enhancements
+
+### Database Connection Property Casting
+
+`BaseConnection` now casts string values coming from `.env` overrides to match the declared type of each connection property. This affects properties that are `null` in the config array and then set via `.env` - such as SQLite3's `synchronous` or `busyTimeout` - which previously arrived as strings and were stored without conversion.
+
+If you extended the SQLite3 handler, review your custom typed properties and update them if needed.
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/WorkerMode.php**
+ - `Config\WorkerMode::$resetEventListeners` has been added, with a default value set to `[]`. See [Reset Event Listeners](worker_mode.md#reset-event-listeners) for details.
+
+### 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:
+
+- **app/Config/Database.php**
+- **app/Config/WorkerMode.php**
diff --git a/docs/installation/upgrade_472.md b/docs/installation/upgrade_472.md
new file mode 100644
index 000000000000..813d21cdb8b8
--- /dev/null
+++ b/docs/installation/upgrade_472.md
@@ -0,0 +1,28 @@
+# Upgrading from 4.7.1 to 4.7.2
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- No config files were changed in this release.
+
+### 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:
+
+- No project files were changed in this release.
diff --git a/docs/installation/upgrade_473.md b/docs/installation/upgrade_473.md
new file mode 100644
index 000000000000..0285fc1042bd
--- /dev/null
+++ b/docs/installation/upgrade_473.md
@@ -0,0 +1,52 @@
+# Upgrading from 4.7.2 to 4.7.3
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Mandatory File Changes
+
+### Worker Mode
+
+If you are using Worker Mode, you must update **public/frankenphp-worker.php** after upgrading. The easiest way is to re-run the install command:
+
+```console
+php spark worker:install --force
+```
+
+## Breaking Changes
+
+### File Validation
+
+The `ext_in` file upload validation rule now checks the client filename extension and verifies that the detected MIME type is associated with that extension. Previously, `ext_in` only checked the MIME-derived guessed extension.
+
+This means files with no client filename extension, or files whose client filename extension does not match the detected MIME type, now fail `ext_in` validation. If your application intentionally accepts such files, remove `ext_in` from those validation rules and use a custom validation rule that matches your application's requirements.
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/Database.php**
+- **app/Config/Events.php**
+- **app/Config/Routes.php**
+- **app/Config/View.php**
+
+### 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:
+
+- **app/Config/Database.php**
+- **app/Config/Events.php**
+- **app/Config/Routes.php**
+- **app/Config/View.php**
diff --git a/docs/installation/upgrade_474.md b/docs/installation/upgrade_474.md
new file mode 100644
index 000000000000..114e506a6482
--- /dev/null
+++ b/docs/installation/upgrade_474.md
@@ -0,0 +1,48 @@
+# Upgrading from 4.7.3 to 4.7.4
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### HTTPS Detection Behind Proxies
+
+For security reasons, `IncomingRequest::isSecure()` no longer trusts the `X-Forwarded-Proto` and `Front-End-Https` headers unless the request comes from a trusted proxy. See the [Security advisory GHSA-7wmf-pw8j-mc78](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-7wmf-pw8j-mc78) for more information.
+
+If your application runs behind a reverse proxy or load balancer that terminates TLS, you must register the proxy in `Config\App::$proxyIPs`, e.g.:
+
+```
+public array $proxyIPs = [
+ '10.0.1.200' => 'X-Forwarded-For',
+ '192.168.5.0/24' => 'X-Forwarded-For',
+];
+```
+
+Otherwise, `isSecure()`, `force_https()`, and `Config\App::$forceGlobalSecureRequests` will treat such requests as non-HTTPS, which may result in redirect loops.
+
+!!! note "Note"
+ On dual-stack servers, a proxy's IPv4 address may be reported as an IPv4-mapped IPv6 address (e.g., `::ffff:192.168.5.21`), which does not match an IPv4 entry such as `192.168.5.0/24`. In that case, add the IPv4-mapped form to `Config\App::$proxyIPs`, e.g., `::ffff:192.168.5.21` or `::ffff:192.168.5.0/120`.
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- No config files were changed in this release.
+
+### 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:
+
+- No project files were changed in this release.
diff --git a/docs/installation/upgrade_475.md b/docs/installation/upgrade_475.md
new file mode 100644
index 000000000000..492b9569d69f
--- /dev/null
+++ b/docs/installation/upgrade_475.md
@@ -0,0 +1,71 @@
+# Upgrading from 4.7.4 to 4.7.5
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### View Parser No Longer Parses Substituted Values
+
+For security reasons, the `Parser` now treats every substituted value strictly as data. A value that contains Parser syntax, such as `{name}`, `{!name!}` or `{name|upper}`, is rendered literally and is never parsed again by a later substitution pass. See the [Security advisory GHSA-vgrf-mv2j-gp8w](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-vgrf-mv2j-gp8w) for more information.
+
+Previously, such a value could be parsed again, depending on the order of the keys in the data array. This behavior was never documented, but code that relied on it will now output the placeholders literally. For example, the following used to render `Hello, Bob!
` and now renders `Hello, {name}!
`:
+
+```php
+$parser->setData([
+ 'greeting' => 'Hello, {name}!',
+ 'name' => 'Bob',
+])->renderString('{greeting}
');
+```
+
+If you need to insert a rendered template fragment into another template, render the fragment first and pass the result as a value, using the `{! !}` syntax so it is not escaped again:
+
+```php
+$greeting = $parser->setData(['name' => 'Bob'])->renderString('Hello, {name}!');
+
+$parser->setData(['greeting' => $greeting])->renderString('{! greeting !}
');
+```
+
+### Built-in Parser Plugin Output
+
+The output of built-in Parser plugins is now treated as rendered data and is not processed again as Parser syntax. This prevents values such as URLs and validation messages from introducing variable or plugin instructions. Applications that relied on placeholders within built-in plugin output must render those fragments explicitly. Custom plugins that transform template code retain their existing behavior and are responsible for keeping untrusted data separate from Parser syntax.
+
+### File Upload Validation
+
+The `is_image`, `mime_in`, and `ext_in` rules now reject client filenames with a PHP handler extension before the final extension, including extensions revealed by `sanitize_filename()` (for example, `shell.p$hp.gif` becomes `shell.php.gif`). Filenames ending in dots, including dots followed by spaces, are also rejected.
+
+These checks apply regardless of the file's contents or the intended meaning of its name. For example, both `shell.php.gif` and an innocent PHP logo named `logo.php.gif` fail validation, even if the latter contains only a GIF image. Rename such files before uploading, for example to `logo-php.gif`. Choosing a generated filename when saving the file does not bypass these validation checks, which run against the client filename before the file is saved.
+
+## Behavior Changes
+
+### CURLRequest Options After a Failed Request
+
+When `Config\CURLRequest::$shareOptions` is `false`, request-specific options are now reset even when the request throws an exception. Per-request `baseURI` and `delay` values are also reset after both successful and failed requests. Constructor defaults remain available for subsequent requests.
+
+If your application retries a failed request, pass its options again and reapply any settings made with `setAuth()`, `setBody()`, `setForm()`, or `setJSON()`. Do not rely on credentials or body data from the failed request remaining on the client.
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- **app/Config/View.php**
+ - `Config\View::$restrictParserConditionals` has been added, with a default value set to `false`. See [Restricting Conditionals](../outgoing/view_parser.md#restricting-conditionals) for details.
+
+### 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:
+
+- **app/Config/View.php**
+- **app/Controllers/BaseController.php**
diff --git a/docs/installation/upgrade_476.md b/docs/installation/upgrade_476.md
new file mode 100644
index 000000000000..35e25905ec6f
--- /dev/null
+++ b/docs/installation/upgrade_476.md
@@ -0,0 +1,76 @@
+# Upgrading from 4.7.5 to 4.7.6
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## Breaking Changes
+
+### File Cache `deleteMatching()` No Longer Matches Subdirectories
+
+For security reasons, `FileHandler::deleteMatching()` now matches the pattern against the names of files in the cache directory only. See the [Security advisory GHSA-9hf2-g3w9-pfmp](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-9hf2-g3w9-pfmp) for more information.
+
+Previously, the pattern was appended to the cache path and passed to `glob()`, so a pattern containing a directory separator, such as `dir/key_*`, also deleted matching files in a subdirectory of the cache directory. Such a pattern now never matches anything and the method returns `0`.
+
+Most applications are not affected. The File handler stores every cache item directly in the cache directory, and the default `Config\Cache::$reservedCharacters` forbid `/` and `\` in cache keys, so cache items could not be stored in subdirectories. If your application places its own files in subdirectories of the cache directory and removed them with `deleteMatching()`, delete them directly instead, for example with the `delete_files()` helper function.
+
+### Model Field Protection for `replace()`
+
+For security reasons, `Model::replace()` now applies field protection:
+
+- With protection enabled, fields outside `$allowedFields` are discarded except for a supplied primary key. Define a non-empty list of fields callers may assign.
+- Non-empty input reduced by filtering to no data or only its primary key now raises a `DataException`. Review calls to ensure they supply allowed data.
+- For trusted operations requiring other columns, explicitly disable protection with `protect(false)` and restore it with `protect(true)` afterward.
+
+See [replace()](../models/model.md#replace) for details and replacement-specific precautions.
+
+### Named Binds Are No Longer Replaced Inside Quoted Values
+
+For security reasons, named bind placeholders (`:name:`) are now replaced only where the SQL parser would see them as part of the query. A placeholder that appears inside a quoted string (`'...'`, `"..."`, `$$...$$` on PostgreSQL, `q'[...]'` on Oracle), inside a quoted identifier (backticks, `"..."`, and `[...]` on SQL Server and SQLite), or inside a SQL comment (`--`, `/* */`, and `#` on MySQL) is now left untouched. This makes named binds behave the same way as question mark binds, which have always ignored markers inside quoted strings. See the [Security advisory GHSA-25rf-99hr-7pmr](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-25rf-99hr-7pmr) for more information.
+
+Previously, such placeholders were replaced as well, so a value that was already escaped and rendered into the query (for example a subquery, a custom `where()` string built with `$db->escape()`, or the `WHERE` binds of `deleteBatch()`) could be mixed with the value of another bind.
+
+This change only concerns queries that place a placeholder inside a quoted string, a quoted identifier, or a comment. With the default escaping, such a placeholder never produced the intended SQL, because the quotes of the escaped value ended up inside the literal or identifier (for example `LIKE '%:name:%'` compiled to `LIKE '%'Rick'%'`). In practice, the only working queries that change their behavior are those that disable escaping for a bind, either with the third parameter of `$db->query()` or by passing a `RawSql` value, and rely on the placeholder being replaced inside a quoted string, a quoted identifier, or a comment: the placeholder is now left in the query literally. For example, the following used to search for `%Rick%` and now searches for the literal string `%:name:%`:
+
+```php
+$db->query("SELECT * FROM users WHERE name LIKE '%:name:%'", ['name' => ['Rick', false]], false);
+```
+
+Move the quotes into the bound value instead, so that the value is escaped as a whole:
+
+```php
+$db->query('SELECT * FROM users WHERE name LIKE :name:', ['name' => '%Rick%']);
+```
+
+## Breaking Enhancements
+
+## Behavior Changes
+
+### Filename Sanitization
+
+`sanitize_filename()` and `Security::sanitizeFilename()` now remove all backslashes. Single backslashes between ordinary characters were already removed in previous versions, so paths such as `folder\file.txt` produce the same result. Ordinary filenames and relative paths with forward slashes are also unchanged. Results can differ for repeated backslashes, literal `\0` sequences, and inputs where removing backslashes exposes sequences stripped by the sanitizer.
+
+Use forward slashes (`/`) for relative paths passed with `$relativePath = true`, including on Windows. If your application stores or compares sanitized filenames created from these inputs, check for changed results and possible filename collisions.
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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
+
+- No config files were changed in this release.
+
+### 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:
+
+- No project files were changed in this release.
diff --git a/docs/installation/upgrade_477.md b/docs/installation/upgrade_477.md
new file mode 100644
index 000000000000..082aa62c50e8
--- /dev/null
+++ b/docs/installation/upgrade_477.md
@@ -0,0 +1,34 @@
+# Upgrading from 4.7.6 to 4.7.7
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- [Composer Installation App Starter Upgrading](installing_composer.md#upgrading)
+- [Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading](installing_composer.md#upgrading_1)
+- [Manual Installation Upgrading](installing_manual.md#upgrading)
+
+## 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 "Note"
+ There are some third-party CodeIgniter modules available to assist with merging changes to the project space: [Explore on Packagist](https://packagist.org/explore/?query=codeigniter4%20updates).
+
+### 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/docs/installation/upgrade_4xx.md b/docs/installation/upgrade_4xx.md
new file mode 100644
index 000000000000..958ac87eb8e3
--- /dev/null
+++ b/docs/installation/upgrade_4xx.md
@@ -0,0 +1,144 @@
+# Upgrading from 3.x to 4.x
+
+CodeIgniter 4 is a rewrite of the framework and is not backwards compatible. It is more appropriate to think of converting your app, rather than upgrading it. Once you have done that, upgrading from one version of CodeIgniter 4 to the next will be straightforward.
+
+The "lean, mean and simple" philosophy has been retained, but the implementation has a lot of differences, compared to CodeIgniter 3.
+
+There is no 12-step checklist for upgrading. Instead, start with a copy of CodeIgniter 4 in a new project folder, [however you wish to install and use it](index.md), and then convert and integrate your app components. We'll try to point out the most important considerations here.
+
+To upgrade your project, there are two major tasks you have to work on. First of all, there are some general adjustments which are significant to every project and have to be handled. The second is the libraries, which make up CodeIgniter and contain some of the most important functions. These libraries operate separately from each other, so you have to look at them one by one.
+
+**Do read the user guide** before embarking on a project conversion!
+
+## General Adjustments
+
+### Downloads
+
+- CI4 is still available as a [ready-to-run zip or tarball](../installation/installing_manual.md).
+- It can also be installed using [Composer](../installation/installing_composer.md).
+
+### Namespaces
+
+- CI4 is built for PHP 8.2+, and everything in the framework is namespaced, except for the helper and lang files.
+
+### Application Structure
+
+!!! important "Important"
+ **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 not to the project root.
+
+ If you use Shared Hosting, see [Deployment to Shared Hosting Services](deployment.md#deployment-to-shared-hosting-services).
+
+- The **application** folder is renamed as **app** and the framework still has **system** folders, with the same interpretation as before.
+- The framework now provides for a **public** folder, intended as the document root for your app.
+- The `defined('BASEPATH') OR exit('No direct script access allowed');` line is not necessary because files outside the **public** folder are not accessible in the standard configuration. And CI4 no longer defines the constant `BASEPATH`, so remove the line in all files.
+- There is also a **writable** folder, to hold cache data, logs, and session data.
+- The **app** folder looks very similar to **application** for CI3, with some name changes, and some subfolders moved to the **writable** folder.
+- There is no longer a nested **application/core** folder, as we have a different mechanism for extending framework components (see below).
+
+### Routing
+
+- The Auto Routing is disabled by default. You need to [define all routes](../incoming/routing.md#setting-routing-rules) by default.
+- If you want to use the Auto Routing in the same way as CI3, you need to enable [Auto Routing (Legacy)](../incoming/routing.md#auto-routing-legacy).
+- CI4 also has an optional new more secure [Auto Routing (Improved)](../incoming/auto_routing_improved.md#auto-routing-improved).
+
+### Model, View and Controller
+
+- CodeIgniter is based on the MVC concept. Thus, the changes to the Model, View and Controller are among the most important things you have to handle.
+- In CodeIgniter 4, models are now located in **app/Models** and you have to add the lines `namespace App\Models;` along with `use CodeIgniter\Model;` right after the opening php tag. The last step is to replace `extends CI_Model` with `extends Model`.
+- The views of CodeIgniter 4 have been moved to **app/Views**. Furthermore, you have to change the syntax of loading views from `$this->load->view('directory_name/file_name')` to `echo view('directory_name/file_name');`.
+- Controllers of CodeIgniter 4 have to be moved to **app/Controllers**. After that, add `namespace App\Controllers;` after the opening php tag. Lastly, replace `extends CI_Controller` with `extends BaseController`.
+- For more information, we recommend the following upgrade guides, which will give you some step-by-step instructions to convert the MVC classes in CodeIgniter4:
+
+- [Upgrade Models](upgrade_models.md)
+- [Upgrade Views](upgrade_views.md)
+- [Upgrade Controllers](upgrade_controllers.md)
+
+### Core Class Changes
+
+- Input
+ - CI3's [Input](http://codeigniter.com/userguide3/libraries/input.html) corresponds to CI4's [IncomingRequest](../incoming/incomingrequest.md).
+ - For historical reasons, CI3 and CI4 used incorrect HTTP method names like "get", "post". Since v4.5.0, CI4 uses the correct HTTP method names like "GET", "POST".
+- Output
+ - CI3's [Output](http://codeigniter.com/userguide3/libraries/output.html) corresponds to CI4's [Responses](../outgoing/response.md).
+
+### Class Loading
+
+- There is no longer a CodeIgniter "superobject", with framework component references magically injected as properties of your controller.
+- Classes are instantiated where needed, and framework components are managed by [Services](../concepts/services.md).
+- The [Autoloader](../concepts/autoloader.md) automatically handles PSR-4 style class locating, within the `App` (**app** folder) and `CodeIgniter` (i.e., **system** folder) top level namespaces, with Composer autoloading support.
+- You can configure the class loading to support whatever application structure you are most comfortable with, including the "HMVC" style.
+- CI4 provides [Factories](../concepts/factories.md) that can load a class and share the instance like `$this->load` in CI3.
+
+### Libraries
+
+- Your app classes can still go inside **app/Libraries**, but they don't have to.
+- Instead of CI3's `$this->load->library('x');` you can now use `$this->x = new \App\Libraries\X();`, following namespaced conventions for your component. Alternatively, you can use [Factories](../concepts/factories.md): `$this->x = \CodeIgniter\Config\Factories::libraries('X');`.
+
+### Helpers
+
+- [Helpers](../general/helpers.md) are pretty much the same as before, though some have been simplified.
+- Since v4.3.0, you can autoload helpers via **app/Config/Autoload.php**, as in CI3.
+- Some helpers from CodeIgniter 3 no longer exist in Version 4. For all these helpers, you have to find a new way to implement your functions. These helpers are [CAPTCHA Helper](https://www.codeigniter.com/userguide3/helpers/captcha_helper.html), [Email Helper](https://www.codeigniter.com/userguide3/helpers/email_helper.html), [Path Helper](https://www.codeigniter.com/userguide3/helpers/path_helper.html), and [Smiley Helper](https://www.codeigniter.com/userguide3/helpers/smiley_helper.html).
+- [Download Helper](https://www.codeigniter.com/userguide3/helpers/download_helper.html) in CI3 was removed. You need to use the Response object where you are using `force_download()`. See [Force File Download](../outgoing/response.md#force-file-download).
+- [Language Helper](https://www.codeigniter.com/userguide3/helpers/language_helper.html) in CI3 was removed. But `lang()` is always available in CI4. See `lang()`.
+- [Typography Helper](https://www.codeigniter.com/userguide3/helpers/typography_helper.html) in CI3 will be [Typography Library](../libraries/typography.md) in CI4.
+- [Directory Helper](https://www.codeigniter.com/userguide3/helpers/directory_helper.html) and [File Helper](https://www.codeigniter.com/userguide3/helpers/file_helper.html) in CI3 will be [Filesystem Helper](../helpers/filesystem_helper.md) in CI4.
+- [String Helper](https://www.codeigniter.com/userguide3/helpers/string_helper.html) functions in CI3 are included in [Text Helper](../helpers/text_helper.md) in CI4.
+- In CI4, `redirect()` is completely changed from CI3's.
+ - [redirect() Documentation CodeIgniter 3.x](https://codeigniter.com/userguide3/helpers/url_helper.html#redirect)
+ - `redirect() Documentation CodeIgniter 4.x`
+ - In CI4, `redirect()` returns a `RedirectResponse` instance instead of redirecting and terminating script execution. You must return it from Controllers or Controller Filters.
+ - Cookies and Headers you set before calling `redirect()` are not automatically carried over to a `RedirectResponse`. You need to call `withCookies()` or `withHeaders()` manually if you want to send them.
+ - You need to change CI3's `redirect('login/form')` to `return redirect()->to('login/form')`.
+
+### Hooks
+
+- [Hooks](https://www.codeigniter.com/userguide3/general/hooks.html) have been replaced by [Events](../extending/events.md).
+- Instead of CI3's `$hook['post_controller_constructor']` you now use `Events::on('post_controller_constructor', ['MyClass', 'MyFunction']);`, with the namespace `CodeIgniter\Events\Events;`.
+- Events are always enabled, and are available globally.
+- The hook points `pre_controller` and `post_controller` have been removed. Use [Controller Filters](../incoming/filters.md) instead.
+- The hook points `display_override` and `cache_override` have been removed because the base methods have been removed.
+- The hook point `post_system` has moved to just before sending the final rendered page.
+
+### Error Handling
+
+- The behavior in CI4 has been slightly changed.
+ - In CI3 the behavior is set in the **index.php** file:
+ - errors with the error level set by `error_reporting()` are logged (but depending on the `log_threshold` setting, they may not be written to the log file).
+ - errors with an error level of `E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR` stop framework processing, regardless of the error level set in `error_reporting()`.
+ - In CI4, the behavior is set in the **app/Config/Boot/{environment}.php** file:
+ - errors with the error level set by `error_reporting()` are logged (but depending on the `Config\Logger::$threshold` setting, they may not be written to the log file).
+ - all errors that are not ignored by `error_reporting()` will stop the framework processing.
+
+### Extending the Framework
+
+- You don't need a **core** folder to hold `MY_...` framework component extensions or replacements.
+- You don't need `MY_X` classes inside your libraries folder to extend or replace CI4 pieces.
+- Make any such classes where you like, and add appropriate service methods in **app/Config/Services.php** to load your components instead of the default ones.
+- See [Creating Core System Classes](../extending/core_classes.md) for details.
+
+## Upgrading Libraries
+
+- Your app classes can still go inside **app/Libraries**, but they don't have to.
+- Instead of CI3's `$this->load->library('x');` you can now use `$this->x = new \App\Libraries\X();`, following namespaced conventions for your component. Alternatively, you can use [Factories](../concepts/factories.md): `$this->x = \CodeIgniter\Config\Factories::libraries('X');`.
+- Some libraries from CodeIgniter 3 no longer exist in Version 4. For all these libraries, you have to find a new way to implement your functions. These libraries are [Calendaring](http://codeigniter.com/userguide3/libraries/calendar.html), [FTP](http://codeigniter.com/userguide3/libraries/ftp.html), [Javascript](http://codeigniter.com/userguide3/libraries/javascript.html), [Shopping Cart](http://codeigniter.com/userguide3/libraries/cart.html), [Trackback](http://codeigniter.com/userguide3/libraries/trackback.html), [XML-RPC /-Server](http://codeigniter.com/userguide3/libraries/xmlrpc.html), and [Zip Encoding](http://codeigniter.com/userguide3/libraries/zip.html).
+- All the other libraries, which exist in both CodeIgniter versions, can be upgraded with some adjustments. The most important and most used libraries received an Upgrade Guide, which will help you with simple steps and examples to adjust your code.
+
+- [Upgrade Configuration](upgrade_configuration.md)
+- [Upgrade Database](upgrade_database.md)
+- [Upgrade Emails](upgrade_emails.md)
+- [Upgrade Encryption](upgrade_encryption.md)
+- [Upgrade Working with Uploaded Files](upgrade_file_upload.md)
+- [Upgrade HTML Tables](upgrade_html_tables.md)
+- [Upgrade Image Manipulation Class](upgrade_images.md)
+- [Upgrade Localization](upgrade_localization.md)
+- [Upgrade Migrations](upgrade_migrations.md)
+- [Upgrade Output Class](upgrade_responses.md)
+- [Upgrade Pagination](upgrade_pagination.md)
+- [Upgrade Routing](upgrade_routing.md)
+- [Upgrade Security](upgrade_security.md)
+- [Upgrade Sessions](upgrade_sessions.md)
+- [Upgrade Validations](upgrade_validations.md)
+- [Upgrade View Parser](upgrade_view_parser.md)
diff --git a/docs/installation/upgrade_configuration.md b/docs/installation/upgrade_configuration.md
new file mode 100644
index 000000000000..acab37f8d559
--- /dev/null
+++ b/docs/installation/upgrade_configuration.md
@@ -0,0 +1,53 @@
+# Upgrade Configuration
+
+## Documentation
+
+- [Config Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/config.html)
+- [Configuration Documentation CodeIgniter 4.x](../general/configuration.md)
+
+## What has been changed
+
+- In CI4, the configurations are now stored in classes which extend `CodeIgniter\Config\BaseConfig`.
+- The **application/config/config.php** in CI3 will be **app/Config/App.php** and some other files like **app/Config/Security.php** for the specific classes.
+- Within the configuration class, the config values are stored in public class properties.
+- The method to fetch config values has been changed.
+
+## Upgrade Guide
+
+1. You have to change the values in the default CI4 config files according to the changes in the CI3 files. The config names are pretty much the same as in CI3.
+2. If you are using custom config files in your CI3 project you have to create those files as new PHP classes in your CI4 project in **app/Config**. These classes should be in the `Config` namespace and should extend `CodeIgniter\Config\BaseConfig`.
+3. Once you have created all custom config classes, you have to copy the variables from the CI3 config into the new CI4 config class as public class properties.
+4. Now, you have to change the config fetching syntax everywhere you fetch config values. The CI3 syntax is something like `$this->config->item('item_name');`. You have to change this into `config('MyConfig')->item_name;`.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+Path: **application/config/site.php**:
+
+```php
+load->database();` with `$db = db_connect();`.
+3. If you use multiple databases, use the following code to load additional databases: `$db = db_connect('group_name');`.
+4. Now you have to change all database queries. The most important change here is to replace `$this->db` with just `$db` and adjust the method name and `$db`. Here are some examples:
+ - `$this->db->query('YOUR QUERY HERE');` to `$db->query('YOUR QUERY HERE');`
+ - `$this->db->simple_query('YOUR QUERY')` to `$db->simpleQuery('YOUR QUERY')`
+ - `$this->db->escape("something")` to `$db->escape("something");`
+ - `$this->db->affected_rows();` to `$db->affectedRows();`
+ - `$query->result();` to `$query->getResult();`
+ - `$query->result_array();` to `$query->getResultArray();`
+ - `echo $this->db->count_all('my_table');` to `echo $db->table('my_table')->countAll();`
+5. To use the new Query Builder Class, you have to initialize the builder: `$builder = $db->table('mytable');`. After that, you can run the queries on the `$builder`. Here are some examples:
+ - `$this->db->get()` to `$builder->get();`
+ - `$this->db->get_where('mytable', array('id' => $id), $limit, $offset);` to `$builder->getWhere(['id' => $id], $limit, $offset);`
+ - `$this->db->select('title, content, date');` to `$builder->select('title, content, date');`
+ - `$this->db->select_max('age');` to `$builder->selectMax('age');`
+ - `$this->db->join('comments', 'comments.id = blogs.id');` to `$builder->join('comments', 'comments.id = blogs.id');`
+ - `$this->db->having('user_id', 45);` to `$builder->having('user_id', 45);`
+6. CI4 does not provide the [Database Caching](https://www.codeigniter.com/userguide3/database/caching.html) layer known from CI3, so if you need to cache the result, use [Caching Driver](../libraries/caching.md) instead.
+7. If you use `limit(0)` in Query Builder, CI4 returns all records instead of no records due to a bug. But since v4.5.0, you can change the incorrect behavior with a setting. So change the setting. See [limit(0) Behavior](../changelogs/v4.5.0.md#limit0-behavior) for details.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+db->select('title')
+ ->where('id', $id)
+ ->limit(10, 20)
+ ->get('mytable');
+```
+
+### CodeIgniter Version 4.x
+
+```php
+table('mytable');
+
+$query = $builder->select('title')
+ ->where('id', $id)
+ ->limit(10, 20)
+ ->get();
+```
diff --git a/docs/installation/upgrade_emails.md b/docs/installation/upgrade_emails.md
new file mode 100644
index 000000000000..7cb5a235c382
--- /dev/null
+++ b/docs/installation/upgrade_emails.md
@@ -0,0 +1,56 @@
+# Upgrade Emails
+
+## Documentation
+
+- [Email Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/email.html)
+- [Email Documentation CodeIgniter 4.x](../libraries/email.md)
+
+## What has been changed
+
+- Only small things like the method names and the loading of the library have changed.
+- The behavior when using the SMTP protocol has been slightly changed. You may not be able to communicate with your SMTP server properly if you use the CI3 settings. See [SSL versus TLS for SMTP Protocol](../libraries/email.md#ssl-versus-tls-for-smtp-protocol) and [Email Preferences](../libraries/email.md#email-preferences).
+
+## Upgrade Guide
+
+1. Within your class, change `$this->load->library('email');` to `$email = service('email');`.
+2. From then on, you have to change `$this->email` to `$email` in every line starting with it.
+3. The methods in the Email class are named slightly differently. All methods, except for `send()`, `attach()`, `printDebugger()` and `clear()`, have `set` as a prefix followed by the previous method name. `bcc()` is now `setBcc()` and so on.
+4. The config attributes in **app/Config/Email.php** have changed. You should have a look at the [Setting Email Preferences](../libraries/email.md#setting-email-preferences) for a list of the new attributes.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+load->library('email');
+
+$this->email->from('your@example.com', 'Your Name');
+$this->email->to('someone@example.com');
+$this->email->cc('another@another-example.com');
+$this->email->bcc('them@their-example.com');
+
+$this->email->subject('Email Test');
+$this->email->message('Testing the email class.');
+
+$this->email->send();
+```
+
+### CodeIgniter Version 4.x
+
+```php
+setFrom('your@example.com', 'Your Name');
+$email->setTo('someone@example.com');
+$email->setCC('another@another-example.com');
+$email->setBCC('them@their-example.com');
+
+$email->setSubject('Email Test');
+$email->setMessage('Testing the email class.');
+
+$email->send();
+```
diff --git a/docs/installation/upgrade_encryption.md b/docs/installation/upgrade_encryption.md
new file mode 100644
index 000000000000..545ff89c6558
--- /dev/null
+++ b/docs/installation/upgrade_encryption.md
@@ -0,0 +1,46 @@
+# Upgrade Encryption
+
+## Documentation
+
+- [Encryption Library Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/encryption.html)
+- [Encryption Service Documentation CodeIgniter 4.x](../libraries/encryption.md)
+
+## What has been changed
+
+- Support for `MCrypt` has been dropped, as that has been deprecated as of PHP 7.2.
+
+## Upgrade Guide
+
+1. Within your configs, `$config['encryption_key'] = 'abc123';` moved from **application/config/config.php** to `public $key = 'abc123';` in **app/Config/Encryption.php**.
+2. If you need to decrypt data encrypted with CI3's Encryption, configure settings to maintain compatibility. See [Configuration to Maintain Compatibility with CI3](../libraries/encryption.md#configuration-to-maintain-compatibility-with-ci3).
+3. Wherever you have used the encryption library, you have to replace `$this->load->library('encryption');` with `$encrypter = service('encrypter');` and change the methods for encrypting and decrypting like in the following code example.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+load->library('encryption');
+
+$plain_text = 'This is a plain-text message!';
+$ciphertext = $this->encryption->encrypt($plain_text);
+
+// Outputs: This is a plain-text message!
+echo $this->encryption->decrypt($ciphertext);
+```
+
+### CodeIgniter Version 4.x
+
+```php
+encrypt($plainText);
+
+// Outputs: This is a plain-text message!
+echo $encrypter->decrypt($ciphertext);
+```
diff --git a/docs/installation/upgrade_file_upload.md b/docs/installation/upgrade_file_upload.md
new file mode 100644
index 000000000000..cbbea8890e99
--- /dev/null
+++ b/docs/installation/upgrade_file_upload.md
@@ -0,0 +1,97 @@
+# Upgrade Working with Uploaded Files
+
+## Documentation
+
+- [File Uploading Class Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/file_uploading.html)
+- [Working with Uploaded Files Documentation CodeIgniter 4.x](../libraries/uploaded_files.md)
+
+## What has been changed
+
+- The functionality of the file upload has changed a lot. You can now check if the file got uploaded without errors and moving / storing files is simpler.
+
+## Upgrade Guide
+
+In CI4 you access uploaded files with `$file = $this->request->getFile('userfile')`. From there you can validate if the file got uploaded successfully with `$file->isValid()`. To store the file you could use `$path = $this->request->getFile('userfile')->store('head_img/', 'user_name.jpg');`. This will store the file in **writable/uploads/head_img/user_name.jpg**.
+
+You have to change your file uploading code to match the new methods.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+load->helper(array('form', 'url'));
+ }
+
+ public function index()
+ {
+ $this->load->view('upload_form', array('error' => ' ' ));
+ }
+
+ public function do_upload()
+ {
+ $config['upload_path'] = './uploads/';
+ $config['allowed_types'] = 'png|jpg|gif';
+ $config['max_size'] = 100;
+ $config['max_width'] = 1024;
+ $config['max_height'] = 768;
+
+ $this->load->library('upload', $config);
+
+ if (! $this->upload->do_upload('userfile')) {
+ $error = array('error' => $this->upload->display_errors());
+
+ $this->load->view('upload_form', $error);
+ } else {
+ $data = array('upload_data' => $this->upload->data());
+
+ $this->load->view('upload_success', $data);
+ }
+ }
+}
+```
+
+### CodeIgniter Version 4.x
+
+```php
+ ' ']);
+ }
+
+ public function do_upload()
+ {
+ $this->validateData([], [
+ 'userfile' => [
+ 'uploaded[userfile]',
+ 'max_size[userfile,100]',
+ 'mime_in[userfile,image/png,image/jpg,image/gif]',
+ 'ext_in[userfile,png,jpg,gif]',
+ 'max_dims[userfile,1024,768]',
+ ],
+ ]);
+
+ $file = $this->request->getFile('userfile');
+
+ if (! $path = $file->store()) {
+ return view('upload_form', ['error' => 'upload failed']);
+ }
+ $data = ['upload_file_path' => $path];
+
+ return view('upload_success', $data);
+ }
+}
+```
diff --git a/docs/installation/upgrade_html_tables.md b/docs/installation/upgrade_html_tables.md
new file mode 100644
index 000000000000..78ae8895f6a9
--- /dev/null
+++ b/docs/installation/upgrade_html_tables.md
@@ -0,0 +1,50 @@
+# Upgrade HTML Tables
+
+## Documentation
+
+- [HTML Table Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/table.html)
+- [HTML Table Documentation CodeIgniter 4.x](../outgoing/table.md)
+
+## What has been changed
+
+- Only small things like the method names and the loading of the library have changed.
+
+## Upgrade Guide
+
+1. Within your class, change `$this->load->library('table');` to `$table = new \CodeIgniter\View\Table();`.
+2. From then on, you have to change `$this->table` to `$table` in every line starting with it. For example: `echo $this->table->generate($query);` will become `echo $table->generate($query);`
+3. The methods in the HTML Table class could be named slightly differently. The most important change in the naming is the switch from underscored method names to camelCase. The method `set_heading()` from version 3 is now named `setHeading()` and so on.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+load->library('table');
+
+$this->table->set_heading('Name', 'Color', 'Size');
+
+$this->table->add_row('Fred', 'Blue', 'Small');
+$this->table->add_row('Mary', 'Red', 'Large');
+$this->table->add_row('John', 'Green', 'Medium');
+
+echo $this->table->generate();
+```
+
+### CodeIgniter Version 4.x
+
+```php
+setHeading('Name', 'Color', 'Size');
+
+$table->addRow('Fred', 'Blue', 'Small');
+$table->addRow('Mary', 'Red', 'Large');
+$table->addRow('John', 'Green', 'Medium');
+
+echo $table->generate();
+```
diff --git a/docs/installation/upgrade_images.md b/docs/installation/upgrade_images.md
new file mode 100644
index 000000000000..2f8c80871985
--- /dev/null
+++ b/docs/installation/upgrade_images.md
@@ -0,0 +1,51 @@
+# Upgrade Image Manipulation Class
+
+## Documentation
+
+- [Image Manipulation Class Documentation CodeIgniter 3.x](https://www.codeigniter.com/userguide3/libraries/image_lib.html)
+- [Image Manipulation Class Documentation CodeIgniter 4.x](../libraries/images.md)
+
+## What has been changed
+
+- The preferences passed to the constructor or `initialize()` method in CI3 have been changed to be specified in the new methods in CI4.
+- Some preferences like `create_thumb` are removed.
+- In CI4, the `save()` method must be called to save the manipulated image.
+- The `display_errors()` method has been removed, and an exception will be thrown if an error occurs.
+
+## Upgrade Guide
+
+1. Within your class, change `$this->load->library('image_lib');` to `$image = \Config\Services::image();`.
+2. Change the preferences passed to the constructor or `initialize()` method to be specified in the corresponding methods.
+3. Call the `save()` method to save the file.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+load->library('image_lib', $config);
+
+$this->image_lib->resize();
+```
+
+### CodeIgniter Version 4.x
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->resize(75, 50, true)
+ ->save('/path/to/image/mypic_thumb.jpg');
+```
diff --git a/docs/installation/upgrade_localization.md b/docs/installation/upgrade_localization.md
new file mode 100644
index 000000000000..58a3d779d73b
--- /dev/null
+++ b/docs/installation/upgrade_localization.md
@@ -0,0 +1,79 @@
+# Upgrade Localization
+
+## Documentation
+
+- [Language Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/language.html)
+- [Localization Documentation CodeIgniter 4.x](../outgoing/localization.md)
+
+## What has been changed
+
+- In CI4 the language files return the language lines as an array.
+
+## Upgrade Guide
+
+1. Specify the default language in **Config/App.php**:
+
+ ```php
+ **.
+
+3. After that you have to change the syntax within the language files. Below in the Code Example you will see how the language array within the file should look.
+
+4. Remove from every file the language loader `$this->lang->load($file, $lang);`.
+
+5. Replace the method to load the language line `$this->lang->line('error_email_missing')` with `echo lang('Errors.errorEmailMissing');`.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+lang->load('error', $lang);
+echo $this->lang->line('error_email_missing');
+```
+
+### CodeIgniter Version 4.x
+
+```php
+ 'You must submit an email address',
+ 'errorURLMissing' => 'You must submit a URL',
+ 'errorUsernameMissing' => 'You must submit a username',
+ 'nested' => [
+ 'error' => [
+ 'message' => 'A specific error message',
+ ],
+ ],
+];
+
+// ...
+
+echo lang('Errors.errorEmailMissing');
+```
diff --git a/docs/installation/upgrade_migrations.md b/docs/installation/upgrade_migrations.md
new file mode 100644
index 000000000000..20f184d99e72
--- /dev/null
+++ b/docs/installation/upgrade_migrations.md
@@ -0,0 +1,132 @@
+# Upgrade Migrations
+
+## Documentation
+
+- [Database Migrations Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/migration.html)
+- [Database Migrations Documentation CodeIgniter 4.x](../dbmgmt/migration.md)
+
+## What has been changed
+
+- First of all, the sequential naming (`001_create_users`, `002_create_posts`) of migrations is no longer supported. Version 4 of CodeIgniter only supports the timestamp scheme (`20121031100537_create_users`, `20121031500638_create_posts`). If you have used sequential naming, you have to rename each migration file.
+- The migration table definition was changed. If you upgrade from CI3 to CI4 and use the same database, you need to upgrade the migration table definition and its data.
+- The migration procedure has also been changed. You can now migrate the database with a simple CLI command:
+
+```console
+php spark migrate
+```
+
+## Upgrade Guide
+
+1. If your v3 project uses sequential migration names, you have to change those to timestamp names.
+2. You have to move all migration files to the new folder **app/Database/Migrations**.
+3. Remove the line `defined('BASEPATH') OR exit('No direct script access allowed');` if it exists.
+4. Add this line just after the opening php tag: `namespace App\Database\Migrations;`.
+5. Below the `namespace App\Database\Migrations;` line add this line: `use CodeIgniter\Database\Migration;`
+6. Replace `extends CI_Migration` with `extends Migration`.
+7. The method names within the `Forge` class have been changed to use camelCase. For example:
+ - `$this->dbforge->add_field` to `$this->forge->addField`
+ - `$this->dbforge->add_key` to `$this->forge->addKey`
+ - `$this->dbforge->create_table` to `$this->forge->createTable`
+ - `$this->dbforge->drop_table` to `$this->forge->dropTable`
+8. (optional) You can change the array syntax from `array(...)` to `[...]`
+9. Upgrade the migration table, if you use the same database.
+ - **(development)** Run the CI4 migration in the development environment or similar, with a brand-new database, to create the new migration table.
+ - **(development)** Export the migration table.
+ - **(production)** Drop (or rename) the existing CI3 migration table.
+ - **(production)** Import the new migration table and the data.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+Path: **application/migrations**:
+
+```php
+dbforge->add_field(array(
+ 'blog_id' => array(
+ 'type' => 'INT',
+ 'constraint' => 5,
+ 'unsigned' => true,
+ 'auto_increment' => true,
+ ),
+ 'blog_title' => array(
+ 'type' => 'VARCHAR',
+ 'constraint' => '100',
+ ),
+ 'blog_description' => array(
+ 'type' => 'TEXT',
+ 'null' => true,
+ ),
+ ));
+ $this->dbforge->add_key('blog_id', true);
+ $this->dbforge->create_table('blog');
+ }
+
+ public function down()
+ {
+ $this->dbforge->drop_table('blog');
+ }
+}
+```
+
+### CodeIgniter Version 4.x
+
+Path: **app/Database/Migrations**:
+
+```php
+forge->addField([
+ 'blog_id' => [
+ 'type' => 'INT',
+ 'constraint' => 5,
+ 'unsigned' => true,
+ 'auto_increment' => true,
+ ],
+ 'blog_title' => [
+ 'type' => 'VARCHAR',
+ 'constraint' => '100',
+ ],
+ 'blog_description' => [
+ 'type' => 'TEXT',
+ 'null' => true,
+ ],
+ ]);
+ $this->forge->addKey('blog_id', true);
+ $this->forge->createTable('blog');
+ }
+
+ public function down()
+ {
+ $this->forge->dropTable('blog');
+ }
+}
+```
+
+## Search & Replace
+
+You can use the following table to search & replace your old CI3 files.
+
+| Search | Replace |
+|--------------------------------|-----------------------------|
+| extends CI_Migration | extends Migration |
+| $this->dbforge->add_field | $this->forge->addField |
+| $this->dbforge->add_key | $this->forge->addKey |
+| $this->dbforge->create_table | $this->forge->createTable |
+| $this->dbforge->drop_table | $this->forge->dropTable |
diff --git a/docs/installation/upgrade_models.md b/docs/installation/upgrade_models.md
new file mode 100644
index 000000000000..16a1318ee612
--- /dev/null
+++ b/docs/installation/upgrade_models.md
@@ -0,0 +1,114 @@
+# Upgrade Models
+
+## Documentation
+
+- [Model Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/general/models.html)
+- [Model Documentation CodeIgniter 4.x](../models/model.md)
+
+## What has been changed
+
+- The CI4 model has much more functionality, including automatic database connection, basic CRUD, in-model validation, and automatic pagination.
+- Since namespaces have been added to CodeIgniter 4, the models must be changed to support namespaces.
+
+## Upgrade Guide
+
+1. First, move all model files to the folder **app/Models**.
+2. Add this line just after the opening php tag: `namespace App\Models;`.
+3. Below the `namespace App\Models;` line add this line: `use CodeIgniter\Model;`.
+4. Replace `extends CI_Model` with `extends Model`.
+5. Add the `protected $table` property and set the table name.
+6. Add the `protected $allowedFields` property and set the array of field names to allow to insert/update.
+7. Instead of CI3's `$this->load->model('x');`, you would now use `$this->x = new X();`, following namespaced conventions for your component. Alternatively, you can use the `model()` function: `$this->x = model('X');`.
+
+If you use sub-directories in your model structure, you have to change the namespace accordingly. For example, if you have a version 3 model located in **application/models/users/user_contact.php**, the namespace has to be `namespace App\Models\Users;` and the model path in version 4 should look like this: **app/Models/Users/UserContact.php**
+
+The new Model in CI4 has a lot of built-in methods, for example, the `find($id)` method. With this, you can find data where the primary key is equal to `$id`. Inserting data is also easier than before. In CI4 there is an `insert($data)` method. You can optionally make use of all those built-in methods and migrate your code to the new methods.
+
+You can find more information on those methods in [Using CodeIgniter's Model](../models/model.md).
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+Path: **application/models**:
+
+```php
+ $title,
+ 'slug' => $slug,
+ 'text' => $text,
+ );
+
+ return $this->db->insert('news', $data);
+ }
+}
+```
+
+### CodeIgniter Version 4.x
+
+Path: **app/Models**:
+
+```php
+ $title,
+ 'slug' => $slug,
+ 'text' => $text,
+ ];
+
+ // Gets the Query Builder for the table, and calls `insert()`.
+ return $this->builder()->insert($data);
+ }
+}
+```
+
+The above code is a direct translation from CI3 to CI4. It uses Query Builder directly in the model. Note that when you use Query Builder directly, you cannot use features in CodeIgniter's Model.
+
+If you want to use CodeIgniter's Model features, the code will be:
+
+```php
+ $title,
+ 'slug' => $slug,
+ 'text' => $text,
+ ];
+
+ // Uses Model's`insert()` method.
+ return $this->insert($data);
+ }
+}
+```
diff --git a/docs/installation/upgrade_pagination.md b/docs/installation/upgrade_pagination.md
new file mode 100644
index 000000000000..5c739206be81
--- /dev/null
+++ b/docs/installation/upgrade_pagination.md
@@ -0,0 +1,55 @@
+# Upgrade Pagination
+
+## Documentation
+
+- [Pagination Class Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/pagination.html)
+- [Pagination Documentation CodeIgniter 4.x](../libraries/pagination.md)
+
+## What has been changed
+
+- You have to change the views and also the controller in order to use the new pagination library.
+- If you want to customize the pagination links, you need to create View Templates.
+- In CI4 the pagination uses the actual page number only. You can't use the starting index (offset) for the items, which is the default in CI3.
+- If you use [CodeIgniter\Model](../models/model.md), you can use the built-in method in the Model class.
+
+## Upgrade Guide
+
+1. Within the views, change the following:
+ - `pagination->create_links(); ?>` to `= $pager->links() ?>`
+2. Within the controller, you have to make the following changes:
+ - You can use the built-in `paginate()` method on every Model. Have a look at the code example below to see how to set up the pagination on a specific model.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+load->library('pagination');
+$config['base_url'] = base_url().'users/index/';
+$config['total_rows'] = $this->db->count_all('users');
+$config['per_page'] = 10;
+$config['uri_segment'] = 3;
+$config['attributes'] = array('class' => 'pagination-link');
+$this->pagination->initialize($config);
+
+$data['users'] = $this->user_model->get_users(FALSE, $config['per_page'], $offset);
+
+$this->load->view('posts/index', $data);
+```
+
+### CodeIgniter Version 4.x
+
+```php
+ $model->paginate(10),
+ 'pager' => $model->pager,
+];
+
+return view('users/index', $data);
+```
diff --git a/docs/installation/upgrade_responses.md b/docs/installation/upgrade_responses.md
new file mode 100644
index 000000000000..eff936ef56a5
--- /dev/null
+++ b/docs/installation/upgrade_responses.md
@@ -0,0 +1,44 @@
+# Upgrade Output Class
+
+## Documentation
+
+- [Output Class Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/output.html)
+- [HTTP Responses Documentation CodeIgniter 4.x](../outgoing/response.md)
+
+## What has been changed
+
+- The Output class has been changed to the Response class.
+- The methods have been renamed.
+
+## Upgrade Guide
+
+1. The methods in the HTTP Response class are named slightly differently. The most important change in the naming is the switch from underscored method names to camelCase. The method `set_content_type()` from version 3 is now named `setContentType()` and so on.
+2. In most cases, you have to change `$this->output` to `$this->response` followed by the method. You can find all methods in [HTTP Responses](../outgoing/response.md).
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+output->set_status_header(404);
+
+// ...
+
+$this->output
+ ->set_content_type('application/json')
+ ->set_output(json_encode(array('foo' => 'bar')));
+```
+
+### CodeIgniter Version 4.x
+
+```php
+response->setStatusCode(404);
+
+// ...
+
+return $this->response->setJSON(['foo' => 'bar']);
+```
diff --git a/docs/installation/upgrade_routing.md b/docs/installation/upgrade_routing.md
new file mode 100644
index 000000000000..d8989853c415
--- /dev/null
+++ b/docs/installation/upgrade_routing.md
@@ -0,0 +1,75 @@
+# Upgrade Routing
+
+## Documentation
+
+- [URI Routing Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/general/routing.html)
+- [URI Routing Documentation CodeIgniter 4.x](../incoming/routing.md)
+
+## What has been changed
+
+- In CI4 the Auto Routing is disabled by default.
+- In CI4 the new more secure [Auto Routing (Improved)](../incoming/auto_routing_improved.md#auto-routing-improved) is introduced.
+- In CI4 the routing is no longer configured by setting the routes as an array.
+- The Wildcard `(:any)` in CI3 will be the Placeholder `(:segment)` in CI4. The `(:any)` in CI4 matches multiple segments. See [URI Routing](../incoming/routing.md#the-behavior-of-any).
+
+## Upgrade Guide
+
+1. If you use the Auto Routing in the same way as CI3, you need to enable [Auto Routing (Legacy)](../incoming/routing.md#auto-routing-legacy).
+
+2. You have to change the syntax of each routing line and append it in **app/Config/Routes.php**. For example:
+
+ - `$route['journals'] = 'blogs';` to `$routes->add('journals', 'Blogs::index');`. This would map to the `index()` method in the `Blogs` controller.
+ - `$route['product/(:any)'] = 'catalog/product_lookup';` to `$routes->add('product/(:segment)', 'Catalog::productLookup');`. Don't forget to replace `(:any)` with `(:segment)`.
+ - `$route['login/(.+)'] = 'auth/login/$1';` to `$routes->add('login/(.+)', 'Auth::login/$1');`
+
+ !!! note "Note"
+ For backward compatibility, `$routes->add()` is used here. But we strongly recommend using [HTTP verb Routes](../incoming/routing.md#http-verb-routes) like `$routes->get()` instead of `$routes->add()` for security.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+Path: **application/config/routes.php**:
+
+```php
+get('/', 'Home::index');
+
+$routes->add('posts/index', 'Posts::index');
+$routes->add('teams/create', 'Teams::create');
+$routes->add('teams/update', 'Teams::update');
+
+$routes->add('posts/create', 'Posts::create');
+$routes->add('posts/update', 'Posts::update');
+$routes->add('drivers/create', 'Drivers::create');
+$routes->add('drivers/update', 'Drivers::update');
+$routes->add('posts/(:segment)', 'Posts::view/$1');
+```
+
+!!! note "Note"
+ For backward compatibility, `$routes->add()` is used here. But we strongly recommend using [HTTP verb Routes](../incoming/routing.md#http-verb-routes) like `$routes->get()` instead of `$routes->add()` for security.
diff --git a/docs/installation/upgrade_security.md b/docs/installation/upgrade_security.md
new file mode 100644
index 000000000000..228d9182a252
--- /dev/null
+++ b/docs/installation/upgrade_security.md
@@ -0,0 +1,80 @@
+# Upgrade Security
+
+## Documentation
+
+- [Security Class Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/security.html)
+- [Security Documentation CodeIgniter 4.x](../libraries/security.md)
+
+!!! note "Note"
+ If you use the [Form Helper](../helpers/form_helper.md) and enable the CSRF filter globally, then `form_open()` will automatically insert a hidden CSRF field in your forms. So you do not have to upgrade this by yourself.
+
+## What has been changed
+
+- The method to implement CSRF tokens to HTML forms has been changed.
+
+## Upgrade Guide
+
+1. To enable CSRF protection in CI4, you have to enable it in **app/Config/Filters.php**:
+
+ ```php
+ [
+ // 'honeypot',
+ 'csrf',
+ ],
+ ];
+
+ // ...
+ }
+ ```
+
+2. Within your HTML forms you have to remove the CSRF input field which looks similar to ` `.
+
+3. Now, within your HTML forms you have to add `= csrf_field() ?>` somewhere in the form body, unless you are using `form_open()`.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+ $this->security->get_csrf_token_name(),
+ 'hash' => $this->security->get_csrf_hash()
+);
+
+?>
+
+
+```
+
+### CodeIgniter Version 4.x
+
+```php
+
+```
diff --git a/docs/installation/upgrade_sessions.md b/docs/installation/upgrade_sessions.md
new file mode 100644
index 000000000000..d685411e4bcd
--- /dev/null
+++ b/docs/installation/upgrade_sessions.md
@@ -0,0 +1,48 @@
+# Upgrade Sessions
+
+## Documentation
+
+- [Session Library Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/sessions.html)
+- [Session Library Documentation CodeIgniter 4.x](../libraries/sessions.md)
+
+## What has been changed
+
+- Only small things like the method names and the loading of the library have changed.
+- The definition of the session table in Database Driver has changed.
+
+## Upgrade Guide
+
+1. Wherever you use the Session Library, replace `$this->load->library('session');` with `$session = session();`.
+2. From then on, you have to replace `$this->session` with `$session`, followed by the new method name, in every line starting with it.
+ - To access session data, use the syntax `$session->item` or `$session->get('item')` instead of the CI3 syntax `$this->session->name`.
+ - To set data, use `$session->set($array);` instead of `$this->session->set_userdata($array);`.
+ - To remove data, use `unset($_SESSION['some_name']);` or `$session->remove('some_name');` instead of `$this->session->unset_userdata('some_name');`.
+ - To mark session data as flashdata, which will only be available for the next request, use `$session->markAsFlashdata('item');` instead of `$this->session->mark_as_flash('item');`
+3. If you use Database Driver, you need to recreate the session table. See [DatabaseHandler Driver](../libraries/sessions.md#databasehandler-driver).
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+load->library('session');
+
+$_SESSION['item'];
+$this->session->item;
+$this->session->userdata('item');
+```
+
+### CodeIgniter Version 4.x
+
+```php
+get('item');
+$session->item;
+session('item');
+```
diff --git a/docs/installation/upgrade_validations.md b/docs/installation/upgrade_validations.md
new file mode 100644
index 000000000000..a87899498398
--- /dev/null
+++ b/docs/installation/upgrade_validations.md
@@ -0,0 +1,165 @@
+# Upgrade Validations
+
+## Library Documentation
+
+- [Form Validation Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/form_validation.html)
+- [Validation Documentation CodeIgniter 4.x](../libraries/validation.md)
+
+## What has been changed
+
+- If you want to change validation error display, you have to set CI4 [validation View templates](../libraries/validation.md#customizing-error-display).
+- CI4 validation has no [Callbacks](http://www.codeigniter.com/userguide3/libraries/form_validation.html#callbacks-your-own-validation-methods) as in CI3. Use [Callable Rules](../libraries/validation.md#using-callable-rule) (since v4.5.0) or [Closure Rules](../libraries/validation.md#using-closure-rule) (since v4.3.0) or [Rule Classes](../libraries/validation.md#using-rule-classes) instead.
+- In CI3, Callbacks/Callable rules were prioritized, but in CI4, Closure/Callable Rules are not prioritized, and are checked in the order in which they are listed.
+- Since v4.5.0, [Callable Rules](../libraries/validation.md#using-callable-rule) have been introduced, but it is a bit different from CI3's [Callable](http://www.codeigniter.com/userguide3/libraries/form_validation.html#callable-use-anything-as-a-rule).
+- CI4 validation format rules do not permit an empty string.
+- CI4 validation never changes your data.
+- Since v4.3.0, `validation_errors()` has been introduced, but the API is different from CI3's.
+
+## Upgrade Guide
+
+1. Within the view which contains the form, you have to change:
+
+ - `` to `= validation_list_errors() ?>`
+
+2. Within the controller, you have to change the following:
+
+ - `$this->load->helper(array('form', 'url'));` to `helper('form');`
+ - remove the line `$this->load->library('form_validation');`
+ - `if ($this->form_validation->run() == FALSE)` to `if (! $this->validateData($data, $rules))` where `$data` is the data to validate, typically, the POST data `$this->request->getPost()`.
+ - `$this->load->view('myform');` to `return view('myform', ['validation' => $this->validator,]);`
+
+3. You have to change the validation rules. The new syntax is to set the rules as an array in the controller:
+
+ ```php
+ validateData($data, [
+ 'name' => 'required|min_length[3]',
+ 'email' => 'required|valid_email',
+ 'phone' => 'required|numeric|max_length[10]',
+ ]);
+ ```
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+Path: **application/views**:
+
+```
+
+
+ My Form
+
+
+
+
+
+
+
+ Username
+
+
+ Password
+
+
+ Password Confirm
+
+
+ Email Address
+
+
+
+
+
+
+
+
+```
+
+Path: **application/controllers**:
+
+```php
+load->helper(array('form', 'url'));
+
+ $this->load->library('form_validation');
+
+ // Set validation rules
+
+ if ($this->form_validation->run() == FALSE) {
+ $this->load->view('myform');
+ } else {
+ $this->load->view('formsuccess');
+ }
+ }
+}
+```
+
+### CodeIgniter Version 4.x
+
+Path: **app/Views**:
+
+```
+
+
+ My Form
+
+
+
+ = validation_list_errors() ?>
+
+ = form_open('form') ?>
+
+ Username
+
+
+ Password
+
+
+ Password Confirm
+
+
+ Email Address
+
+
+
+
+
+
+
+
+```
+
+Path: **app/Controllers**:
+
+```php
+request->getPost();
+
+ if (! $this->validateData($data, [
+ // Validation rules
+ ])) {
+ return view('myform');
+ }
+
+ return view('formsuccess');
+ }
+}
+```
diff --git a/docs/installation/upgrade_view_parser.md b/docs/installation/upgrade_view_parser.md
new file mode 100644
index 000000000000..9c0757c37c26
--- /dev/null
+++ b/docs/installation/upgrade_view_parser.md
@@ -0,0 +1,49 @@
+# Upgrade View Parser
+
+## Documentation
+
+- [Template Parser Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/libraries/parser.html)
+- [View Parser Documentation CodeIgniter 4.x](../outgoing/view_parser.md)
+
+## What has been changed
+
+- You have to change the implementation and loading of the Parser Library.
+- The Views can be copied from CI3. Usually, no changes are required there.
+
+## Upgrade Guide
+
+1. Wherever you use the View Parser Library, replace `$this->load->library('parser');` with `$parser = service('parser');`.
+2. You have to change the render part in your controller from `$this->parser->parse('blog_template', $data);` to `return $parser->setData($data)->render('blog_template');`.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+```php
+load->library('parser');
+
+$data = array(
+ 'blog_title' => 'My Blog Title',
+ 'blog_heading' => 'My Blog Heading'
+);
+
+$this->parser
+ ->parse('blog_template', $data);
+```
+
+### CodeIgniter Version 4.x
+
+```php
+ 'My Blog Title',
+ 'blog_heading' => 'My Blog Heading',
+];
+
+return $parser->setData($data)->render('blog_template');
+```
diff --git a/docs/installation/upgrade_views.md b/docs/installation/upgrade_views.md
new file mode 100644
index 000000000000..448109e422f3
--- /dev/null
+++ b/docs/installation/upgrade_views.md
@@ -0,0 +1,71 @@
+# Upgrade Views
+
+## Documentation
+
+- [View Documentation CodeIgniter 3.x](http://codeigniter.com/userguide3/general/views.html)
+- [View Documentation CodeIgniter 4.x](../outgoing/views.md)
+
+## What has been changed
+
+- Your views look much like before, but they are invoked differently: instead of CI3's `$this->load->view('x');`, you can use `return view('x');`.
+- CI4 supports [View Cells](../outgoing/view_cells.md) to build your response in pieces, and [View Layouts](../outgoing/view_layouts.md) for page layout.
+- The [Template Parser](../outgoing/view_parser.md) is still there, and substantially enhanced.
+
+## Upgrade Guide
+
+1. First, move all views to the folder **app/Views**.
+2. Change the loading syntax of views in every script where you load views:
+ - from `$this->load->view('directory_name/file_name')` to `return view('directory_name/file_name');`
+ - from `$content = $this->load->view('file', $data, TRUE);` to `$content = view('file', $data);`
+3. (optional) You can change the echo syntax in views from `` to `= $title ?>`
+4. Remove the line `defined('BASEPATH') OR exit('No direct script access allowed');` if it exists.
+
+## Code Example
+
+### CodeIgniter Version 3.x
+
+Path: **application/views**:
+
+```php
+
+
+
+
+
+
+
+ My Todo List
+
+
+
+
+
+```
+
+### CodeIgniter Version 4.x
+
+Path: **app/Views**:
+
+```php
+
+
+ = esc($title) ?>
+
+
+ = esc($heading) ?>
+
+ My Todo List
+
+
+
+
+
+```
diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md
new file mode 100644
index 000000000000..b7ffc93ad405
--- /dev/null
+++ b/docs/installation/upgrading.md
@@ -0,0 +1,96 @@
+# Upgrading From a Previous Version
+
+Please read the upgrade notes corresponding to the version you are upgrading from.
+
+See also [Backward Compatibility Notes](backward_compatibility_notes.md).
+
+The manual mentions changes to the **project space**. To perform the update, you need to compare the original files from **vendor/codeigniter4/framework** or from the ZIP archive and make changes.
+
+For example, the original **app/Config/App.php** will be located in **vendor/codeigniter4/framework/app/Config/App.php**. Alternatively, replace it with a new file and add your previous lines.
+
+!!! note "Note"
+ If you don't know what version of CodeIgniter you are currently running, you can get it from [the Debug Toolbar](../testing/debugging.md#the-debug-toolbar), or simply echo the constant `\CodeIgniter\CodeIgniter::CI_VERSION`.
+
+- [Backward Compatibility Notes](backward_compatibility_notes.md)
+- [Upgrading from 4.7.6 to 4.7.7](upgrade_477.md)
+- [Upgrading from 4.7.5 to 4.7.6](upgrade_476.md)
+- [Upgrading from 4.7.4 to 4.7.5](upgrade_475.md)
+- [Upgrading from 4.7.3 to 4.7.4](upgrade_474.md)
+- [Upgrading from 4.7.2 to 4.7.3](upgrade_473.md)
+- [Upgrading from 4.7.1 to 4.7.2](upgrade_472.md)
+- [Upgrading from 4.7.0 to 4.7.1](upgrade_471.md)
+- [Upgrading from 4.6.x to 4.7.0](upgrade_470.md)
+- [Upgrading from 4.6.4 to 4.6.5](upgrade_465.md)
+- [Upgrading from 4.6.3 to 4.6.4](upgrade_464.md)
+- [Upgrading from 4.6.2 to 4.6.3](upgrade_463.md)
+- [Upgrading from 4.6.1 to 4.6.2](upgrade_462.md)
+- [Upgrading from 4.6.0 to 4.6.1](upgrade_461.md)
+- [Upgrading from 4.5.8 to 4.6.0](upgrade_460.md)
+- [Upgrading from 4.5.7 to 4.5.8](upgrade_458.md)
+- [Upgrading from 4.5.6 to 4.5.7](upgrade_457.md)
+- [Upgrading from 4.5.5 to 4.5.6](upgrade_456.md)
+- [Upgrading from 4.5.4 to 4.5.5](upgrade_455.md)
+- [Upgrading from 4.5.3 to 4.5.4](upgrade_454.md)
+- [Upgrading from 4.5.2 to 4.5.3](upgrade_453.md)
+- [Upgrading from 4.5.1 to 4.5.2](upgrade_452.md)
+- [Upgrading from 4.5.0 to 4.5.1](upgrade_451.md)
+- [Upgrading from 4.4.8 to 4.5.0](upgrade_450.md)
+- [Upgrading from 4.4.7 to 4.4.8](upgrade_448.md)
+- [Upgrading from 4.4.6 to 4.4.7](upgrade_447.md)
+- [Upgrading from 4.4.5 to 4.4.6](upgrade_446.md)
+- [Upgrading from 4.4.4 to 4.4.5](upgrade_445.md)
+- [Upgrading from 4.4.3 to 4.4.4](upgrade_444.md)
+- [Upgrading from 4.4.2 to 4.4.3](upgrade_443.md)
+- [Upgrading from 4.4.1 to 4.4.2](upgrade_442.md)
+- [Upgrading from 4.4.0 to 4.4.1](upgrade_441.md)
+- [Upgrading from 4.3.8 to 4.4.0](upgrade_440.md)
+- [Upgrading from 4.3.7 to 4.3.8](upgrade_438.md)
+- [Upgrading from 4.3.6 to 4.3.7](upgrade_437.md)
+- [Upgrading from 4.3.5 to 4.3.6](upgrade_436.md)
+- [Upgrading from 4.3.4 to 4.3.5](upgrade_435.md)
+- [Upgrading from 4.3.3 to 4.3.4](upgrade_434.md)
+- [Upgrading from 4.3.2 to 4.3.3](upgrade_433.md)
+- [Upgrading from 4.3.1 to 4.3.2](upgrade_432.md)
+- [Upgrading from 4.3.0 to 4.3.1](upgrade_431.md)
+- [Upgrading from 4.2.12 to 4.3.0](upgrade_430.md)
+- [Upgrading from 4.2.11 to 4.2.12](upgrade_4212.md)
+- [Upgrading from 4.2.10 to 4.2.11](upgrade_4211.md)
+- [Upgrading from 4.2.9 to 4.2.10](upgrade_4210.md)
+- [Upgrading from 4.2.7 to 4.2.8](upgrade_428.md)
+- [Upgrading from 4.2.6 to 4.2.7](upgrade_427.md)
+- [Upgrading from 4.2.5 to 4.2.6](upgrade_426.md)
+- [Upgrading from 4.2.3 to 4.2.5](upgrade_425.md)
+- [Upgrading from 4.2.2 to 4.2.3](upgrade_423.md)
+- [Upgrading from 4.2.1 to 4.2.2](upgrade_422.md)
+- [Upgrading from 4.2.0 to 4.2.1](upgrade_421.md)
+- [Upgrading from 4.1.9 to 4.2.0](upgrade_420.md)
+- [Upgrading from 4.1.7 to 4.1.8](upgrade_418.md)
+- [Upgrading from 4.1.6 to 4.1.7](upgrade_417.md)
+- [Upgrading from 4.1.5 to 4.1.6](upgrade_416.md)
+- [Upgrading from 4.1.4 to 4.1.5](upgrade_415.md)
+- [Upgrading from 4.1.3 to 4.1.4](upgrade_414.md)
+- [Upgrading from 4.1.2 to 4.1.3](upgrade_413.md)
+- [Upgrading from 4.1.1 to 4.1.2](upgrade_412.md)
+- [Upgrading from 4.0.5 to 4.1.0 or 4.1.1](upgrade_410.md)
+- [Upgrading from 4.0.4 to 4.0.5](upgrade_405.md)
+- [Upgrading from 4.0.x to 4.0.4](upgrade_404.md)
+- [Upgrading from 3.x to 4.x](upgrade_4xx.md)
+ - [Upgrade Models](upgrade_models.md)
+ - [Upgrade Views](upgrade_views.md)
+ - [Upgrade Controllers](upgrade_controllers.md)
+ - [Upgrade Configuration](upgrade_configuration.md)
+ - [Upgrade Database](upgrade_database.md)
+ - [Upgrade Emails](upgrade_emails.md)
+ - [Upgrade Encryption](upgrade_encryption.md)
+ - [Upgrade Working with Uploaded Files](upgrade_file_upload.md)
+ - [Upgrade HTML Tables](upgrade_html_tables.md)
+ - [Upgrade Image Manipulation Class](upgrade_images.md)
+ - [Upgrade Localization](upgrade_localization.md)
+ - [Upgrade Migrations](upgrade_migrations.md)
+ - [Upgrade Output Class](upgrade_responses.md)
+ - [Upgrade Pagination](upgrade_pagination.md)
+ - [Upgrade Routing](upgrade_routing.md)
+ - [Upgrade Security](upgrade_security.md)
+ - [Upgrade Sessions](upgrade_sessions.md)
+ - [Upgrade Validations](upgrade_validations.md)
+ - [Upgrade View Parser](upgrade_view_parser.md)
diff --git a/docs/installation/worker_mode.md b/docs/installation/worker_mode.md
new file mode 100644
index 000000000000..58bad275ae99
--- /dev/null
+++ b/docs/installation/worker_mode.md
@@ -0,0 +1,266 @@
+# Worker Mode
+
+!!! success "Available from version 4.7.0"
+
+!!! important "Important"
+ Worker Mode is currently **experimental**. The only officially supported worker implementation is **FrankenPHP**, which is backed by the PHP Foundation.
+
+## Introduction
+
+### What is Worker Mode?
+
+Worker Mode is a performance optimization feature that allows CodeIgniter to handle multiple HTTP requests within the same bootstrapped application. PHP-FPM also reuses worker processes, but normally initializes and tears down the application for each request.
+
+#### Traditional PHP vs Worker Mode
+
+**Traditional PHP (PHP-FPM)**
+
+In traditional PHP, each HTTP request goes through this cycle:
+
+1. Web server receives the request and passes it to an available PHP-FPM worker process
+2. PHP loads the required files, using cached bytecode when OPcache is enabled
+3. Framework bootstraps: autoloader, configuration, services, routes
+4. Database connections are established
+5. Request is processed and response is sent
+6. Request resources are released, including non-persistent connections
+7. Application state is torn down, and the worker process can handle another request
+
+This "shared nothing" architecture is simple and safe, but inefficient for high-traffic applications because steps 2-4 repeat for every single request.
+
+**Worker Mode**
+
+In Worker Mode, the lifecycle changes dramatically:
+
+1. Worker process starts and performs one-time initialization:
+ - Loads and parses all required files (cached in OPcache)
+ - Bootstraps framework: autoloader, configuration, services, routes
+ - Establishes database and cache connections
+2. For each incoming request:
+ - Reuses existing connections and cached resources
+ - Resets only request-specific state (superglobals, request/response objects)
+ - Processes request and sends response
+ - Cleans up request-specific data
+3. Worker continues running, ready for the next request
+
+This approach eliminates redundant initialization work and connection overhead, resulting in **2-3x performance improvements** for typical database-driven applications.
+
+### How CodeIgniter Manages State
+
+The key challenge in Worker Mode is preventing state from leaking between requests. CodeIgniter handles this through several mechanisms:
+
+**Services Reset**
+Most services are destroyed and recreated for each request. Only services listed in `$persistentServices` survive between requests.
+
+**Factories Reset**
+All Factories (Models, Config instances) are reset between requests, ensuring fresh instances without stale data.
+
+**Superglobals Isolation**
+Request data (`$_GET`, `$_POST`, `$_SERVER`, etc.) is properly isolated per request through the Superglobals service.
+
+**Connection Persistence**
+Database and cache connections are validated at request start and reused if healthy. Unhealthy connections are automatically re-established.
+
+## Getting Started
+
+### Installation
+
+1. Install FrankenPHP following the [official documentation](https://frankenphp.dev/)
+
+ You can use a static binary or Docker. Here, we assume a static binary, which can be [downloaded directly](https://github.com/php/frankenphp/releases/).
+
+2. Install the Worker Mode template files using the spark command:
+
+ ```console
+ php spark worker:install
+ ```
+
+ This command creates two files:
+
+ - **Caddyfile** - FrankenPHP configuration file with worker mode enabled
+ - **public/frankenphp-worker.php** - Worker entry point that handles the request loop
+
+3. Configure your worker settings in **app/Config/WorkerMode.php** if needed. The defaults are recommended for most applications.
+
+!!! note "Note"
+ When changing the project directory, see the section [Renaming or Relocating the Application Directory](../general/managing_apps.md#renaming-or-relocating-the-application-directory) and update your **public/frankenphp-worker.php**.
+
+### Running the Worker
+
+Start FrankenPHP using the generated Caddyfile:
+
+```console
+frankenphp run
+```
+
+The server will start with worker mode enabled, handling requests through the worker entry point.
+
+To run in the background (daemon mode):
+
+```console
+frankenphp start
+```
+
+### Uninstalling
+
+To remove the Worker Mode template files:
+
+```console
+php spark worker:uninstall
+```
+
+This removes the **Caddyfile** and **public/frankenphp-worker.php** files.
+
+## Performance Benchmarks
+
+Worker Mode typically provides **2-3x performance improvements** for database-driven applications. The actual gains depend on your application's characteristics:
+
+| Scenario | Expected Improvement |
+|----|----|
+| **Simple endpoints** | Applications returning minimal JSON responses show modest improvements (10-30%), as there's little bootstrap overhead to eliminate. |
+| **Database queries** | Endpoints performing database queries typically see **2-3x** improvements from connection reuse and reduced initialization overhead. |
+| **Complex bootstrap** | Applications with many services, routes, or configuration see the largest gains, as this overhead is eliminated entirely. |
+
+### Worker Count Considerations
+
+Total throughput scales with worker count. Match worker count to your typical concurrency patterns and available CPU cores. Too few workers limit concurrency; too many waste memory.
+
+### First Request Latency
+
+The initial request to each worker is slower due to bootstrap and connection establishment. Subsequent requests benefit from cached resources and persistent connections.
+
+## Configuration
+
+All worker mode configuration is managed through the **app/Config/WorkerMode.php** file.
+
+### Configuration Options
+
+| Option | Type | Description |
+|----|----|----|
+| **$persistentServices** | array | Services that persist across requests and are not reset. Services not in this list are destroyed after each request to prevent state leakage. Default: `['autoloader', 'locator', 'exceptions', 'commands', 'codeigniter', 'superglobals', 'routes', 'cache']` |
+| **$resetEventListeners** | array | Event names whose listeners are removed between requests. Use this when you register event listeners inside other event callbacks rather than at the top level of **Config/Events.php**, which would cause them to accumulate across requests. Default: `[]` |
+| **$forceGarbageCollection** | bool | Whether to force garbage collection after each request. `true` (default, recommended): Prevents memory leaks. `false`: Relies on PHP's automatic garbage collection. |
+
+### Persistent Services
+
+The `$persistentServices` array controls which services survive between requests. The default configuration includes:
+
+| Service | Purpose |
+|----|----|
+| `autoloader` | PSR-4 autoloading configuration. Safe to persist as class maps don't change. |
+| `locator` | File locator for finding framework files. Caches file paths for performance. |
+| `exceptions` | Exception handler. Stateless, safe to reuse. |
+| `commands` | CLI commands registry. Only used during worker startup. |
+| `codeigniter` | Main application instance. Orchestrates the request/response cycle. |
+| `superglobals` | Superglobals wrapper. Properly isolated per request internally. |
+| `routes` | Router configuration. Route definitions don't change between requests. |
+| `cache` | Cache service. Maintains connections to cache backends (Redis, Memcached). |
+
+!!! warning "Warning"
+ Adding services to `$persistentServices` without understanding their state management can cause data leakage between requests. Only persist services that are truly stateless or manage their own request isolation.
+
+### Reset Event Listeners
+
+!!! success "Available from version 4.7.1"
+
+Event listeners registered at the top level of **Config/Events.php** are loaded once at worker startup and persist correctly across requests. However, if you register a listener inside another event's callback, it will be re-registered on every request and accumulate:
+
+```php
+';
+ $foo = 'foobarbaz!';
+
+ // Save into the cache for 5 minutes
+ cache()->save('foo', $foo, 300);
+}
+
+echo $foo;
+```
+
+You can grab an instance of the cache engine directly through the global function `service()`:
+
+```php
+get('foo');
+```
+
+## Configuring the Cache
+
+All configuration for the cache engine is done in **app/Config/Cache.php**. In that file, the following items are available.
+
+### $handler
+
+This is the name of the handler that should be used as the primary handler when starting up the engine. Available names are: apcu, dummy, file, memcached, redis, predis, wincache.
+
+### $backupHandler
+
+In the case that the first choice `$handler` is not available, this is the next cache handler to load. This is commonly the `File` handler since the file system is always available, but may not fit more complex, multi-server setups.
+
+### $prefix
+
+If you have more than one application using the same cache storage, you can add a custom prefix string here that is prepended to all key names.
+
+### $ttl
+
+The default number of seconds to save items when none is specified.
+
+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.
+
+### $file
+
+This is an array of settings specific to the **File** handler to determine how it should save the cache files.
+
+### $memcached
+
+This is an array of servers that will be used when using the **Memcached** handler.
+
+### $redis
+
+The settings for the Redis server that you wish to use when using the **Redis** and **Predis** handlers.
+
+## Command-Line Tools
+
+CodeIgniter ships with several [commands](../cli/spark_commands.md) that are available from the command line to help you work with Cache. These tools are not required to use the Cache driver, but might help you.
+
+### cache:clear
+
+Clears the current system caches:
+
+```console
+php spark cache:clear
+```
+
+### cache:info
+
+Shows file cache information in the current system:
+
+```console
+php spark cache:info
+```
+
+!!! note "Note"
+ This command only supports the File cache handler.
+
+## Class Reference
+
+## CodeIgniter\Cache
+
+### CacheInterface
+
+#### isSupported()
+
+- **Returns**: `true` if supported, `false` if not
+- **Return type**: `bool`
+
+#### get($key): mixed
+
+- **Parameters**
+ - **$key** `string` Cache item name
+
+- **Returns**: Item value or `null` if not found
+
+- **Return type**: `mixed`
+
+This method will attempt to fetch an item from the cache store. If the item does not exist, the method will return null.
+
+Example:
+
+```php
+get('my_cached_item');
+```
+
+#### remember(string $key, int $ttl, Closure $callback)
+
+- **Parameters**
+ - **$key** `string` Cache item name
+ - **$ttl** `int` Time to live in seconds
+ - **$callback** `Closure` Callback to invoke when the cache item returns null
+
+- **Returns**: The value of the cache item
+
+- **Return type**: `mixed`
+
+Gets an item from the cache. If `null` was returned, this will invoke the callback and save the result. Either way, this will return the value.
+
+#### save(string $key, $data[, int $ttl = 60])
+
+- **Parameters**
+ - **$key** `string` Cache item name
+ - **$data** `mixed` the data to save
+ - **$ttl** `int` Time To Live, in seconds (default 60)
+
+- **Returns**: `true` on success, `false` on failure
+
+- **Return type**: `bool`
+
+This method will save an item to the cache store. If saving fails, the method will return `false`.
+
+Example:
+
+```php
+save('cache_item_id', 'data_to_cache');
+```
+
+#### delete($key): bool
+
+- **Parameters**
+ - **$key** `string` name of cached item
+
+- **Returns**: `true` on success, `false` on failure
+
+- **Return type**: `bool`
+
+This method will delete a specific item from the cache store. If item deletion fails, the method will return false.
+
+Example:
+
+```php
+delete('cache_item_id');
+```
+
+#### deleteMatching($pattern): integer
+
+- **Parameters**
+ - **$pattern** `string` glob-style pattern to match cached item keys
+
+- **Returns**: number of deleted items
+
+- **Return type**: `integer`
+
+This method will delete multiple items from the cache store at once by matching their keys against a glob-style pattern. It will return the total number of deleted items.
+
+!!! important "Important"
+ This method is only implemented for the File, Redis and Predis handlers. Due to limitations, it couldn't be implemented for the Memcached and Wincache handlers.
+
+!!! note "Note"
+ With the File handler, the pattern is matched against cache file names only and cannot match files in subdirectories. A pattern containing a forward slash (`/`) therefore never matches.
+
+Example:
+
+```php
+deleteMatching('prefix_*'); // deletes all items of which keys start with "prefix_"
+$cache->deleteMatching('*_suffix'); // deletes all items of which keys end with "_suffix"
+```
+
+For more information on glob-style syntax, please see [Glob (programming)](https://en.wikipedia.org/wiki/Glob_(programming)#Syntax).
+
+#### increment($key[, $offset = 1]): mixed
+
+- **Parameters**
+ - **$key** `string` Cache ID
+ - **$offset** `int` Step/value to add
+
+- **Returns**: New value on success, `false` on failure
+
+- **Return type**: `mixed`
+
+Performs atomic incrementation of a raw stored value.
+
+Example:
+
+```php
+increment('iterator'); // 'iterator' is now 3
+$cache->increment('iterator', 3); // 'iterator' is now 6
+```
+
+#### decrement($key[, $offset = 1]): mixed
+
+- **Parameters**
+ - **$key** `string` Cache ID
+ - **$offset** `int` Step/value to reduce by
+
+- **Returns**: New value on success, `false` on failure
+
+- **Return type**: `mixed`
+
+Performs atomic decrementation of a raw stored value.
+
+Example:
+
+```php
+decrement('iterator'); // 'iterator' is now 5
+$cache->decrement('iterator', 2); // 'iterator' is now 3
+```
+
+!!! important "Important"
+ Memcached counters are unsigned and saturate at `0` instead of going negative. Decrementing a key that does not yet exist initializes it to `0` (rather than `-$offset`, as the File, Redis, and Predis handlers do), and decrementing an existing counter below `0` clamps it at `0`.
+
+#### clean()
+
+- **Returns**: `true` on success, `false` on failure
+- **Return type**: `bool`
+
+This method will 'clean' the entire cache. If the deletion of the cache files fails, the method will return false.
+
+Example:
+
+```php
+clean();
+```
+
+#### getCacheInfo()
+
+- **Returns**: Information on the entire cache database
+- **Return type**: `mixed`
+
+This method will return information on the entire cache.
+
+Example:
+
+```php
+getCacheInfo());
+```
+
+!!! note "Note"
+ The information returned and the structure of the data are dependent on which adapter is being used.
+
+#### getMetadata(string $key)
+
+- **Parameters**
+ - **$key** `string` Cache item name
+
+- **Returns**: Metadata for the cached item. `null` for missing items, or an array with at least the "expire" key for absolute epoch expiry (`null` for never expires).
+
+- **Return type**: `array|null`
+
+This method will return detailed information on a specific item in the cache.
+
+Example:
+
+```php
+getMetadata('my_cached_item'));
+```
+
+!!! note "Note"
+ The information returned and the structure of the data are dependent on which adapter is being used. Some adapters (File, Memcached, Wincache) still return `false` for missing items.
+
+#### validateKey(string $key, string $prefix)
+
+- **Parameters**
+ - **$key** `string` Potential cache key
+ - **$prefix** `string` Optional prefix
+
+- **Returns**: The verified and prefixed key. If the key exceeds the driver's max key length it will be hashed.
+
+- **Return type**: `string`
+
+This method is used by handler methods to check that keys are valid. It will throw an `InvalidArgumentException` for non-strings, invalid characters, and empty lengths.
+
+Example:
+
+```php
+ '127.0.0.1',
+ 'port' => 11211,
+ 'weight' => 1,
+ 'raw' => false,
+ ];
+
+ // ...
+}
+```
+
+For more information on Memcached, please see [https://www.php.net/memcached](https://www.php.net/memcached).
+
+### WinCache Caching
+
+Under Windows, you can also utilize the WinCache driver.
+
+For more information on WinCache, please see [https://www.php.net/wincache](https://www.php.net/wincache).
+
+### Redis Caching
+
+Redis is an in-memory key-value store which can operate in LRU cache mode. To use it, you need the [Redis server and phpredis PHP extension](https://github.com/phpredis/phpredis).
+
+Config options to connect to the Redis server are stored in the cache configuration file. Available options are:
+
+```php
+ '127.0.0.1',
+ 'password' => null,
+ 'port' => 6379,
+ 'async' => false, // specific to Predis and ignored by the native Redis extension
+ 'persistent' => false,
+ 'timeout' => 0,
+ 'database' => 0,
+ ];
+
+ // ...
+}
+```
+
+For more information on Redis, please see [https://redis.io](https://redis.io).
+
+### Predis Caching
+
+Predis is a flexible and feature-complete PHP client library for the Redis key-value store. To use it, run the following from the command line inside your project root:
+
+```console
+composer require predis/predis
+```
+
+For more information on Predis, please see [https://github.com/nrk/predis](https://github.com/nrk/predis).
+
+### Dummy Cache
+
+This is a caching backend that will always 'miss.' It stores no data, but lets you keep your caching code in place in environments that don't support your chosen cache.
diff --git a/docs/libraries/cookies.md b/docs/libraries/cookies.md
new file mode 100644
index 000000000000..164db2c9f1de
--- /dev/null
+++ b/docs/libraries/cookies.md
@@ -0,0 +1,800 @@
+# Cookies
+
+An **HTTP cookie** (web cookie, browser cookie) is a small piece of data that a server sends to the user's web browser. The browser may store it and send it back with later requests to the same server. Typically, it's used to tell if two requests came from the same browser - keeping a user logged in, for example. It remembers stateful information for the stateless HTTP protocol.
+
+Cookies are mainly used for three purposes:
+
+- **Session management**: Logins, shopping carts, game scores, or anything else the server should remember
+- **Personalization**: User preferences, themes, and other settings
+- **Tracking**: Recording and analyzing user behavior
+
+To help you efficiently send cookies to browsers, CodeIgniter provides the `CodeIgniter\Cookie\Cookie` class to abstract the cookie interaction.
+
+## Creating Cookies
+
+There are currently four ways to create a new `Cookie` value object.
+
+```php
+ new DateTime('+2 hours'),
+ 'prefix' => '__Secure-',
+ 'path' => '/',
+ 'domain' => '',
+ 'secure' => true,
+ 'httponly' => true,
+ 'raw' => false,
+ 'samesite' => Cookie::SAMESITE_LAX,
+ ],
+);
+
+// Supplying a Set-Cookie header string
+$cookie = Cookie::fromHeaderString(
+ 'remember_token=f699c7fd18a8e082d0228932f3acd40e1ef5ef92efcedda32842a211d62f0aa6; Path=/; Secure; HttpOnly; SameSite=Lax',
+ false, // raw
+);
+
+// Using the fluent builder interface
+$cookie = (new Cookie('remember_token'))
+ ->withValue('f699c7fd18a8e082d0228932f3acd40e1ef5ef92efcedda32842a211d62f0aa6')
+ ->withPrefix('__Secure-')
+ ->withExpires(new DateTime('+2 hours'))
+ ->withPath('/')
+ ->withDomain('')
+ ->withSecure(true)
+ ->withHTTPOnly(true)
+ ->withSameSite(Cookie::SAMESITE_LAX);
+
+// Using the global function `cookie` which implicitly calls `new Cookie()`
+$cookie = cookie('remember_token', 'f699c7fd18a8e082d0228932f3acd40e1ef5ef92efcedda32842a211d62f0aa6');
+```
+
+When constructing the `Cookie` object, only the `name` attribute is required. All others are optional. If the optional attributes are not modified, their values will be filled with the default values saved in the `Cookie` class.
+
+### Overriding Defaults
+
+To override the defaults currently stored in the class, you can pass a `Config\Cookie` instance or an array of defaults to the static `Cookie::setDefaults()` method.
+
+```php
+ 0,
+ 'samesite' => Cookie::SAMESITE_STRICT,
+];
+Cookie::setDefaults($myDefaults);
+$cookie = new Cookie('login_token');
+```
+
+Passing the `Config\Cookie` instance or an array to `Cookie::setDefaults()` will effectively overwrite your defaults and will persist until new defaults are passed.
+
+#### Changing Defaults for a Limited Time
+
+If you do not want this behavior but only want to change defaults for a limited time, you can take advantage of the return value of `Cookie::setDefaults()`, which is the old defaults array.
+
+```php
+ new DateTime('2025-02-14 00:00:00', new DateTimeZone('UTC')),
+ 'prefix' => '__Secure-',
+ 'path' => '/',
+ 'domain' => '',
+ 'secure' => true,
+ 'httponly' => true,
+ 'raw' => false,
+ 'samesite' => Cookie::SAMESITE_LAX,
+ ],
+);
+
+$cookie->getName(); // 'remember_token'
+$cookie->getPrefix(); // '__Secure-'
+$cookie->getPrefixedName(); // '__Secure-remember_token'
+$cookie->getExpiresTimestamp(); // UNIX timestamp
+$cookie->getExpiresString(); // 'Fri, 14 Feb 2025 00:00:00 GMT'
+$cookie->isExpired(); // false
+$cookie->getMaxAge(); // the difference from time() to expires
+$cookie->isRaw(); // false
+$cookie->isSecure(); // true
+$cookie->getPath(); // '/'
+$cookie->getDomain(); // ''
+$cookie->isHTTPOnly(); // true
+$cookie->getSameSite(); // 'Lax'
+
+// additional getter
+$cookie->getId(); // '__Secure-remember_token;;/'
+
+// when using `setcookie()`'s alternative signature on PHP 7.3+
+// you can easily use the `getOptions()` method to supply the
+// $options parameter
+$cookie->getOptions();
+```
+
+## Immutable Cookies
+
+A new `Cookie` instance is an immutable value object representation of an HTTP cookie. Since it is immutable, modifying any of the instance's attributes will not affect the original instance. The modification **always** returns a new instance. You need to retain this new instance in order to use it.
+
+```php
+getName(); // 'login_token'
+
+$cookie->withName('remember_token');
+$cookie->getName(); // 'login_token'
+
+$new = $cookie->withName('remember_token');
+$new->getName(); // 'remember_token'
+```
+
+## Validating a Cookie's Attributes
+
+An HTTP cookie is regulated by several specifications that need to be followed in order to be accepted by browsers. Thus, when creating or modifying certain attributes of the `Cookie`, these are validated in order to check if they follow the specifications.
+
+A `CookieException` is thrown if violations were reported.
+
+### Validating the Name Attribute
+
+A cookie name can be any US-ASCII character, except for the following:
+
+- control characters;
+- spaces or tabs;
+- separator characters, such as `( ) < > @ , ; : \ " / [ ] ? = { }`
+
+If the `$raw` parameter is set to `true`, this validation will be strictly enforced. This is because PHP's [setcookie()](https://www.php.net/manual/en/function.setcookie.php) and [setrawcookie()](https://www.php.net/manual/en/function.setrawcookie.php) will reject cookies with invalid names. Additionally, cookie names cannot be an empty string.
+
+### Validating the Value Attribute
+
+If setting the `$raw` parameter to `true`, the cookie value will also be validated. It must not contain control characters, spaces, tabs, or separator characters (`, ;`) as [setrawcookie()](https://www.php.net/manual/en/function.setrawcookie.php) will reject them.
+
+### Validating the Path Attribute
+
+The cookie path must not contain control characters, spaces, tabs, or separator characters (`, ;`) as [setcookie()](https://www.php.net/manual/en/function.setcookie.php) and [setrawcookie()](https://www.php.net/manual/en/function.setrawcookie.php) will reject them.
+
+### Validating the Domain Attribute
+
+The cookie domain must not contain control characters, spaces, tabs, or separator characters (`, ;`) as [setcookie()](https://www.php.net/manual/en/function.setcookie.php) and [setrawcookie()](https://www.php.net/manual/en/function.setrawcookie.php) will reject them.
+
+### Validating the Prefix Attribute
+
+The cookie prefix must not contain control characters, spaces, tabs, or separator characters (`= , ; \t \r \n \v \f \0`) as [setcookie()](https://www.php.net/manual/en/function.setcookie.php) and [setrawcookie()](https://www.php.net/manual/en/function.setrawcookie.php) will reject them.
+
+When using the `__Secure-` prefix, cookies must be set with the `$secure` flag set to `true`. If using the `__Host-` prefix, cookies must exhibit the following:
+
+- `$secure` flag set to `true`
+- `$domain` is empty
+- `$path` must be `/`
+
+### Validating the SameSite Attribute
+
+The SameSite attribute accepts three values:
+
+- **Lax**: Cookies are not sent on normal cross-site subrequests (for example, to load images or frames into a third-party site), but are sent when a user is navigating to the origin site (*i.e.* when following a link).
+- **Strict**: Cookies will only be sent in a first-party context and not be sent along with requests initiated by third-party websites.
+- **None**: Cookies will be sent in all contexts, *i.e.* in responses to both first-party and cross-origin requests.
+
+CodeIgniter, however, allows you to set the SameSite attribute to an empty string. When an empty string is provided, the default SameSite setting saved in the `Cookie` class is used. You can change the default SameSite by using `Cookie::setDefaults()` as discussed above.
+
+Recent cookie specifications have changed such that modern browsers are required to give a default SameSite if nothing is provided. This default is `Lax`. If you have set the SameSite to be an empty string and your default SameSite is also an empty string, your cookie will be given the `Lax` value.
+
+If the SameSite is set to `None`, you need to make sure that `Secure` is also set to `true`.
+
+When writing the SameSite attribute, the `Cookie` class accepts any of the values case-insensitively. You can also take advantage of the class's constants to make this easier.
+
+```php
+ 3600 * 2, // Expires in 2 hours
+ ],
+);
+
+$response->setCookie($cookie);
+```
+
+You can also use the `set_cookie()` helper function:
+
+```php
+ 3600 * 2, // Expires in 2 hours
+ ],
+);
+
+set_cookie($cookie);
+```
+
+## Using the Cookie Store
+
+!!! note "Note"
+ Normally, there is no need to use CookieStore directly.
+
+The `CookieStore` class represents an immutable collection of `Cookie` objects.
+
+### Getting the Store from Response
+
+The `CookieStore` instance can be accessed from the current `Response` object.
+
+```php
+getCookieStore();
+```
+
+### Creating CookieStore
+
+CodeIgniter provides three other ways to create a new instance of the `CookieStore`.
+
+```php
+has('login_token');
+
+// check if cookie is in the current Response's cookie collection
+cookies()->has('login_token');
+service('response')->hasCookie('remember_token');
+
+// using the cookie helper to check the current Response
+// not available to v4.1.1 and lower
+helper('cookie');
+has_cookie('login_token');
+```
+
+### Getting Cookies in Store
+
+Retrieving a `Cookie` instance in a cookie collection is very easy:
+
+```php
+get('login_token');
+
+// getting cookie in the current Response's cookie collection
+cookies()->get('login_token');
+service('response')->getCookie('remember_token');
+
+// using the cookie helper to get cookie from the Response's cookie collection
+helper('cookie');
+get_cookie('remember_token');
+```
+
+When getting a `Cookie` instance directly from a `CookieStore`, an invalid name will throw a `CookieException`.
+
+```php
+get('unknown_cookie');
+```
+
+When getting a `Cookie` instance from the current `Response`'s cookie collection, an invalid name will just return `null`.
+
+```php
+get('unknown_cookie'); // null
+```
+
+If no arguments are supplied when getting cookies from the `Response`, all `Cookie` objects in store will be displayed.
+
+```php
+display(); // array of Cookie objects
+
+// or even from the Response
+service('response')->getCookies();
+```
+
+!!! note "Note"
+ The helper function `get_cookie()` gets the cookie from the current `Request` object, not from `Response`. This function checks the `$_COOKIE` array to see if that cookie is set and fetches it right away.
+
+### Adding/Removing Cookies in Store
+
+As previously mentioned, `CookieStore` objects are immutable. You need to save the modified instance in order to work on it. The original instance is left unchanged.
+
+```php
+put(new Cookie('admin_token', 'yes'));
+
+// removing a Cookie instance
+$new = $store->remove('login_token');
+```
+
+!!! note "Note"
+ Removing a cookie from the store **DOES NOT** delete it from the browser. If you intend to delete a cookie *from the browser*, you must put an empty-value cookie with the same name into the store.
+
+When interacting with the cookies in store in the current `Response` object, you can safely add or delete cookies without worrying about the immutable nature of the cookie collection. The `Response` object will replace the instance with the modified instance.
+
+```php
+setCookie('admin_token', 'yes');
+service('response')->deleteCookie('login_token');
+
+// using the cookie helper
+helper('cookie');
+set_cookie('admin_token', 'yes');
+delete_cookie('login_token');
+```
+
+### Dispatching Cookies in Store
+
+!!! important "Important"
+ This method was deprecated as of version 4.1.6 and was removed in 4.6.0.
+
+More often than not, you do not need to concern yourself with manually sending cookies. CodeIgniter will do this for you. However, if you really need to manually send cookies, you can use the `dispatch` method. Just like when sending other headers, you need to make sure the headers are not yet sent by checking the value of `headers_sent()`.
+
+```php
+dispatch(); // After dispatch, the collection is now empty.
+```
+
+## Cookie Personalization
+
+Sane defaults are already in place inside the `Cookie` class to ensure the smooth creation of cookie objects. However, you may wish to define your own settings by changing the following settings in the `Config\Cookie` class in the **app/Config/Cookie.php** file.
+
+| Setting | Options/Types | Default | Description |
+|----|----|----|----|
+| **$prefix** | `string` | `''` | Prefix to prepend to the cookie name. |
+| **$expires** | `DateTimeInterface|string|int` | `0` | The expires timestamp. |
+| **$path** | `string` | `/` | The path property of the cookie. |
+| **$domain** | `string` | `''` | The domain property of the cookie. |
+| **$secure** | `true`/`false` | `false` | Whether to send it over secure HTTPS. |
+| **$httponly** | `true`/`false` | `true` | Whether it is inaccessible to JavaScript. |
+| **$samesite** | `Lax`/`None`/`Strict` | `Lax` | The SameSite attribute. |
+| **$raw** | `true`/`false` | `false` | Whether to dispatch it using `setrawcookie()`. |
+
+At runtime, you can manually supply a new default using the `Cookie::setDefaults()` method.
+
+## Class Reference
+
+## CodeIgniter\Cookie
+
+### Cookie
+
+#### setDefaults([$config = []])
+
+- **Parameters**
+ - \Config\Cookie|array `$config` The configuration array or instance
+
+- **Return type**: `array`
+
+- **Returns**: The old defaults
+
+Set the default attributes to a Cookie instance by injecting the values from the `Config\Cookie` config or an array.
+
+#### fromHeaderString(string $header[, bool $raw = false])
+
+- **Parameters**
+ - **$header** `string` The `Set-Cookie` header string
+ - **$raw** `bool` Whether this cookie is not to be URL encoded and sent via `setrawcookie()`
+
+- **Return type**: `Cookie`
+
+- **Returns**: `Cookie` instance
+
+- **Throws**: `CookieException`
+
+Create a new Cookie instance from a `Set-Cookie` header.
+
+#### \__construct(string $name[, string $value = ''[, array $options = []]])
+
+- **Parameters**
+ - **$name** `string` The cookie name
+ - **$value** `string` The cookie value
+ - **$options** `array` The cookie options
+
+- **Return type**: `Cookie`
+
+- **Returns**: `Cookie` instance
+
+- **Throws**: `CookieException`
+
+Construct a new Cookie instance.
+
+#### getId()
+
+- **Return type**: `string`
+- **Returns**: The ID used in indexing in the cookie collection.
+
+#### getPrefix(): string
+
+#### getName(): string
+
+#### getPrefixedName(): string
+
+#### getValue(): string
+
+#### getExpiresTimestamp(): int
+
+#### getExpiresString(): string
+
+#### isExpired(): bool
+
+#### getMaxAge(): int
+
+#### getDomain(): string
+
+#### getPath(): string
+
+#### isSecure(): bool
+
+#### isHTTPOnly(): bool
+
+#### getSameSite(): string
+
+#### isRaw(): bool
+
+#### getOptions(): array
+
+#### withRaw([bool $raw = true])
+
+- **Parameters**
+ - **$raw** `bool`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with the URL encoding option updated.
+
+#### withPrefix([string $prefix = ''])
+
+- **Parameters**
+ - **$prefix** `string`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new prefix.
+
+#### withName(string $name)
+
+- **Parameters**
+ - **$name** `string`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new name.
+
+#### withValue(string $value)
+
+- **Parameters**
+ - **$value** `string`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new value.
+
+#### withExpires($expires)
+
+- **Parameters**
+ - **$expires** `DateTimeInterface|string|int`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new cookie expires time.
+
+#### withExpired()
+
+- **Return type**: `Cookie`
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie that will expire from the browser.
+
+#### withNeverExpiring()
+
+!!! important "Important"
+ This method was deprecated as of version 4.2.6 and was removed in 4.6.0.
+
+- **Return type**: `Cookie`
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie that will virtually never expire.
+
+#### withDomain(?string $domain)
+
+- **Parameters**
+ - **$domain** `string|null`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new domain.
+
+#### withPath(?string $path)
+
+- **Parameters**
+ - **$path** `string|null`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new path.
+
+#### withSecure([bool $secure = true])
+
+- **Parameters**
+ - **$secure** `bool`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new "Secure" attribute.
+
+#### withHTTPOnly([bool $httponly = true])
+
+- **Parameters**
+ - **$httponly** `bool`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new "HttpOnly" attribute.
+
+#### withSameSite(string $samesite)
+
+- **Parameters**
+ - **$samesite** `string`
+
+- **Return type**: `Cookie`
+
+- **Returns**: new `Cookie` instance
+
+Creates a new Cookie with a new "SameSite" attribute.
+
+#### toHeaderString()
+
+- **Return type**: `string`
+- **Returns**: Returns the string representation that can be passed as a header string.
+
+#### toArray()
+
+- **Return type**: `array`
+- **Returns**: Returns the array representation of the Cookie instance.
+
+### CookieStore
+
+#### fromCookieHeaders(array $headers[, bool $raw = false])
+
+- **Parameters**
+ - **$headers** `array` Array of `Set-Cookie` headers
+ - **$raw** `bool` Whether not to use URL encoding
+
+- **Return type**: `CookieStore`
+
+- **Returns**: `CookieStore` instance
+
+- **Throws**: `CookieException`
+
+Creates a CookieStore from an array of `Set-Cookie` headers.
+
+#### \__construct(array $cookies)
+
+- **Parameters**
+ - **$cookies** `array` Array of `Cookie` objects
+
+- **Return type**: `CookieStore`
+
+- **Returns**: `CookieStore` instance
+
+- **Throws**: `CookieException`
+
+#### has(string $name[, string $prefix = ''[, ?string $value = null]]): bool
+
+- **Parameters**
+ - **$name** `string` Cookie name
+ - **$prefix** `string` Cookie prefix
+ - **$value** `string|null` Cookie value
+
+- **Return type**: `bool`
+
+- **Returns**: Checks if a `Cookie` object identified by name and prefix is present in the collection.
+
+#### get(string $name[, string $prefix = '']): Cookie
+
+- **Parameters**
+ - **$name** `string` Cookie name
+ - **$prefix** `string` Cookie prefix
+
+- **Return type**: `Cookie`
+
+- **Returns**: Retrieves an instance of Cookie identified by a name and prefix.
+
+- **Throws**: `CookieException`
+
+#### put(Cookie $cookie): CookieStore
+
+- **Parameters**
+ - **$cookie** `Cookie` A Cookie object
+
+- **Return type**: `CookieStore`
+
+- **Returns**: new `CookieStore` instance
+
+Store a new cookie and return a new collection. The original collection is left unchanged.
+
+#### remove(string $name[, string $prefix = '']): CookieStore
+
+- **Parameters**
+ - **$name** `string` Cookie name
+ - **$prefix** `string` Cookie prefix
+
+- **Return type**: `CookieStore`
+
+- **Returns**: new `CookieStore` instance
+
+Removes a cookie from a collection and returns an updated collection. The original collection is left unchanged.
+
+#### dispatch(): void
+
+!!! important "Important"
+ This method was deprecated as of version 4.1.6 and was removed in 4.6.0.
+
+- **Return type**: `void`
+
+Dispatches all cookies in store.
+
+#### display(): array
+
+- **Return type**: `array`
+- **Returns**: Returns all cookie instances in store.
+
+#### clear(): void
+
+- **Return type**: `void`
+
+Clears the cookie collection.
diff --git a/docs/libraries/cors.md b/docs/libraries/cors.md
new file mode 100644
index 000000000000..73a17f9af65b
--- /dev/null
+++ b/docs/libraries/cors.md
@@ -0,0 +1,208 @@
+# Cross-Origin Resource Sharing (CORS)
+
+!!! success "Available from version 4.5.0"
+
+Cross-Origin Resource Sharing (CORS) is an HTTP-header based security mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.
+
+CORS works by adding headers to HTTP requests and responses to indicate whether the requested resource can be shared across different origins, helping to prevent malicious attacks like cross-site request forgery (CSRF) and data theft.
+
+If you are not familiar with CORS and CORS headers, please read the [MDN documentation on CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers).
+
+CodeIgniter provides the CORS filter and helper class.
+
+## Configuring CORS
+
+### Setting Default Config
+
+CORS can be configured in **app/Config/Cors.php**.
+
+At a minimum, the following items in the `$default` property must be set:
+
+- `allowedOrigins`: List explicitly the Origin(s) you want to allow.
+- `allowedHeaders`: List explicitly the HTTP headers you want to allow.
+- `allowedMethods`: List explicitly the HTTP methods you want to allow.
+
+!!! warning "Warning"
+ Based on the principle of least privilege, only the minimum necessary Origin, Methods, and Headers should be allowed.
+
+If you send credentials (e.g., cookies) with a cross-origin request, set `supportsCredentials` to `true`.
+
+### Enabling CORS
+
+To enable CORS, you need to do two things:
+
+1. Set the `cors` filter on the routes that permit CORS.
+2. Add **OPTIONS** routes for CORS Preflight Requests.
+
+#### Set against Routes
+
+You can set the `cors` filter on routes in **app/Config/Routes.php**.
+
+E.g.,
+
+```php
+group('', ['filter' => 'cors'], static function (RouteCollection $routes): void {
+ $routes->resource('product');
+
+ $routes->options('product', static function () {
+ // Implement processing for normal non-preflight OPTIONS requests,
+ // if necessary.
+ $response = response();
+ $response->setStatusCode(204);
+ $response->setHeader('Allow:', 'OPTIONS, GET, POST, PUT, PATCH, DELETE');
+
+ return $response;
+ });
+ $routes->options('product/(:any)', static function () {});
+});
+```
+
+Don't forget to add OPTIONS routes for Preflight Requests, because Controller Filters (except for Required Filters) do not work if the route does not exist.
+
+The CORS filter handles all Preflight Requests, so the closure controllers for the OPTIONS routes are not normally called.
+
+#### Set in Config\Filters
+
+Alternatively, you can set the `cors` filter on URI paths in **app/Config/Filters.php**.
+
+E.g.,
+
+```php
+ [
+ 'before' => ['api/*'],
+ 'after' => ['api/*'],
+ ],
+ ];
+}
+```
+
+Don't forget to add OPTIONS routes for Preflight Requests, because Controller Filters (except for Required Filters) do not work if the route does not exist.
+
+E.g.,
+
+```php
+group('', ['filter' => 'cors'], static function (RouteCollection $routes): void {
+ $routes->options('api/(:any)', static function () {});
+});
+```
+
+The CORS filter handles all Preflight Requests, so the closure controller for the OPTIONS routes is not normally called.
+
+### Checking Routes and Filters
+
+After configuration, you can check the routes and filters with the [spark routes](../incoming/routing.md#spark-routes) command.
+
+### Setting Another Config
+
+If you want to use a different configuration than the default configuration, add a property to **app/Config/Cors.php**.
+
+For example, add the `$api` property.
+
+```php
+ ['https://app.example.com'],
+ 'allowedOriginsPatterns' => [],
+ 'supportsCredentials' => true,
+ 'allowedHeaders' => ['Authorization', 'Content-Type'],
+ 'exposedHeaders' => [],
+ 'allowedMethods' => ['GET', 'POST', 'PUT', 'DELETE'],
+ 'maxAge' => 7200,
+ ];
+}
+```
+
+The property name (`api` in the above example) will become the configuration name.
+
+Then, specify the property name as the filter argument like `cors:api`:
+
+```php
+group('api', ['filter' => 'cors:api'], static function (RouteCollection $routes): void {
+ $routes->resource('user');
+
+ $routes->options('user', static function () {});
+ $routes->options('user/(:any)', static function () {});
+});
+```
+
+You can also use [Filter Arguments](../incoming/filters.md#filter-arguments).
+
+## Class Reference
+
+## CodeIgniter\HTTP
+
+### Cors
+
+#### addResponseHeaders(RequestInterface $request, ResponseInterface $response): ResponseInterface
+
+- **Parameters**
+ - **$request** `RequestInterface` Request instance
+ - **$response** `ResponseInterface` Response instance
+
+- **Returns**: Response instance
+
+- **Return type**: `ResponseInterface`
+
+Adds response headers for CORS.
+
+#### handlePreflightRequest(RequestInterface $request, ResponseInterface $response): ResponseInterface
+
+- **Parameters**
+ - **$request** `RequestInterface` Request instance
+ - **$response** `ResponseInterface` Response instance
+
+- **Returns**: Response instance
+
+- **Return type**: `ResponseInterface`
+
+Handles Preflight Requests.
+
+#### isPreflightRequest(IncomingRequest $request): bool
+
+- **Parameters**
+ - **$request** `IncomingRequest` Request instance
+
+- **Returns**: True if it is a Preflight Request.
+
+- **Return type**: `bool`
+
+Checks if the request is a Preflight Request.
diff --git a/docs/libraries/curlrequest.md b/docs/libraries/curlrequest.md
new file mode 100644
index 000000000000..c3cbefcd0fa1
--- /dev/null
+++ b/docs/libraries/curlrequest.md
@@ -0,0 +1,612 @@
+# CURLRequest Class
+
+The `CURLRequest` class is a lightweight HTTP client based on CURL that allows you to talk to other websites and servers. It can be used to get the contents of a Google search, retrieve a web page or image, or communicate with an API, among many other things.
+
+This class is modeled after the [Guzzle HTTP Client](http://docs.guzzlephp.org/en/latest/) library since it is one of the more widely used libraries. Where possible, the syntax has been kept the same so that if your application needs something a little more powerful than what this library provides, you will have to change very little to move over to use Guzzle.
+
+!!! note "Note"
+ This class requires the [cURL Library](https://www.php.net/manual/en/book.curl.php) to be installed in your version of PHP. This is a very common library that is typically available, but not all hosts will provide it, so please check with your host to verify if you run into problems.
+
+## Config for CURLRequest
+
+### Sharing Connection
+
+!!! success "Available from version 4.7.0"
+
+By default, this option is enabled with the constants `CURL_LOCK_DATA_CONNECT` and `CURL_LOCK_DATA_DNS`.
+
+If you want to share connections between requests, set `$shareConnectionOptions` to an array of [CURL_LOCK_DATA_\*](https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect) constants in **app/Config/CURLRequest.php**:
+
+```php
+
+ *
+ * @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect
+ */
+ public array $shareConnection = [
+ CURL_LOCK_DATA_CONNECT,
+ CURL_LOCK_DATA_DNS,
+ ];
+
+ // ...
+}
+```
+
+Or, when you want to disable it, just change it to an empty array:
+
+```php
+
+ *
+ * @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect
+ */
+ public array $shareConnection = [];
+
+ // ...
+}
+```
+
+### Sharing Options
+
+!!! important "Important"
+ This setting exists only for backward compatibility. Do not use it in new projects. Even if you are already using it, we recommend that you disable it.
+
+!!! note "Note"
+ Since v4.4.0, the default value has been changed to `false`.
+
+If you want to share all the options between requests, set `$shareOptions` to `true` in **app/Config/CURLRequest.php**:
+
+```php
+ 'http://example.com/api/v1/',
+ 'timeout' => 3,
+];
+$client = service('curlrequest', $options);
+```
+
+!!! note "Note"
+ When `$shareOptions` is false, the default options passed to the class constructor will be used for all requests. Other options will be reset after sending a request.
+
+When creating the class manually, you need to pass a few dependencies in. The first parameter is an instance of the `Config\App` class. The second parameter is a URI instance. The third parameter is a Response object. The fourth parameter is the optional default `$options` array:
+
+```php
+request('GET', 'https://api.github.com/user', [
+ 'auth' => ['user', 'pass'],
+]);
+```
+
+!!! important "Important"
+ By default, CURLRequest will throw `HTTPException` if the HTTP code returned is greater than or equal to 400. If you want to get the response, see the [http_errors](#http_errors) option.
+
+!!! note "Note"
+ When `$shareOptions` is false, the options passed to the method will be used for the request. After the request succeeds or throws an exception, they will be cleared, including per-request `baseURI` and `delay` values. The constructor defaults will be restored. To retry a failed request, pass its options again and reapply any settings made with `setAuth()`, `setBody()`, `setForm()`, or `setJSON()`. If you want to use the options for all requests, pass the options in the constructor.
+
+Since the response is an instance of `CodeIgniter\HTTP\Response`, you have all of the normal information available to you:
+
+```php
+getStatusCode();
+echo $response->getBody();
+echo $response->header('Content-Type');
+$language = $response->negotiateLanguage(['en', 'fr']);
+```
+
+While the `request()` method is the most flexible, you can also use the following shortcut methods. They each take the URL as the first parameter and an array of options as the second:
+
+```php
+get('http://example.com');
+$client->delete('http://example.com');
+$client->head('http://example.com');
+$client->options('http://example.com');
+$client->patch('http://example.com');
+$client->put('http://example.com');
+$client->post('http://example.com');
+```
+
+#### Base URI
+
+A `baseURI` can be set as one of the options during the instantiation of the class. This allows you to set a base URI, and then make all requests with that client using relative URLs. This is especially handy when working with APIs:
+
+```php
+ 'https://example.com/api/v1/',
+]);
+
+// GET http:example.com/api/v1/photos
+$client->get('photos');
+
+// GET http:example.com/api/v1/photos/13
+$client->delete('photos/13');
+```
+
+When a relative URI is provided to the `request()` method or any of the shortcut methods, it will be combined with the baseURI according to the rules described by [RFC 3986, section 5.2](https://tools.ietf.org/html/rfc3986#section-5.2). To save you some time, here are some examples of how the combinations are resolved.
+
+| baseURI | URI | Result |
+|---------------------|----------------|------------------------|
+| http://foo.com | /bar | http://foo.com/bar |
+| http://foo.com/foo | /bar | http://foo.com/bar |
+| http://foo.com/foo | bar | http://foo.com/bar |
+| http://foo.com/foo/ | bar | http://foo.com/foo/bar |
+| http://foo.com | http://baz.com | http://baz.com |
+| http://foo.com/?bar | bar | http://foo.com/bar |
+
+### Using Responses
+
+Each `request()` call returns a Response object that contains a lot of useful information and some helpful methods. The most commonly used methods let you determine the response itself.
+
+You can get the status code and reason phrase of the response:
+
+```php
+getStatusCode(); // 200
+$reason = $response->getReasonPhrase(); // OK
+```
+
+You can retrieve headers from the response:
+
+```php
+getHeaderLine('Content-Type');
+
+// Get all headers
+foreach ($response->headers() as $name => $value) {
+ echo $name . ': ' . $response->getHeaderLine($name) . "\n";
+}
+```
+
+The body can be retrieved using the `getBody()` method:
+
+```php
+getBody();
+```
+
+The body is the raw body provided by the remote server. If the content type requires formatting, you will need to ensure that your script handles that:
+
+```php
+header('content-type'), 'application/json')) {
+ $body = json_decode($body);
+}
+```
+
+## Request Options
+
+This section describes all of the available options you may pass into the constructor, the `request()` method, or any of the shortcut methods.
+
+### allow_redirects
+
+By default, cURL will not follow any "Location:" headers the remote servers send back. The `allow_redirects` option allows you to modify how that works.
+
+If you set the value to `true`, then it will follow redirects:
+
+```php
+request('GET', 'http://example.com', ['allow_redirects' => true]);
+/*
+ * Sets the following defaults:
+ * 'max' => 5, // Maximum number of redirects to follow before stopping
+ * 'strict' => true, // Ensure POST requests stay POST requests through redirects
+ * 'protocols' => ['http', 'https'] // Restrict redirects to one or more protocols
+ */
+```
+
+!!! warning "Warning"
+ Please note that enabling redirects may redirect to a URL that you do not expect and may enable SSRF attacks.
+
+Setting it to `false` will apply the default settings to the request:
+
+```php
+request('GET', 'http://example.com', ['allow_redirects' => false]);
+```
+
+You can pass in an array as the value of the `allow_redirects` option to specify new settings in place of the defaults:
+
+```php
+request('GET', 'http://example.com', ['allow_redirects' => [
+ 'max' => 10,
+ 'protocols' => ['https'], // Force HTTPS domains only.
+]]);
+```
+
+!!! note "Note"
+ Following redirects does not work when PHP's `open_basedir` restriction is enabled.
+
+### auth
+
+Allows you to provide Authentication details for [HTTP Basic](https://www.ietf.org/rfc/rfc2069.txt) and [Digest](https://www.ietf.org/rfc/rfc2069.txt) authentication. Your script may have to do extra work to support Digest authentication - this simply passes the username and password along for you. The value must be an array where the first element is the username, and the second is the password. The third parameter should be the type of authentication to use, either `basic` or `digest`:
+
+```php
+request('GET', 'http://example.com', ['auth' => ['username', 'password', 'digest']]);
+```
+
+### body
+
+There are two ways to set the body of the request for request types that support it, like PUT or POST. The first way is to use the `setBody()` method:
+
+```php
+setBody($body)->request('PUT', 'http://example.com');
+```
+
+The second method is by passing a `body` option in. This is provided to maintain Guzzle API compatibility, and functions the exact same way as the previous example. The value must be a string:
+
+```php
+request('PUT', 'http://example.com', ['body' => $body]);
+```
+
+### cert
+
+To specify the location of a PEM-formatted client-side certificate, pass a string with the full path to the file as the `cert` option. If a password is required, set the value to an array with the first element as the path to the certificate, and the second as the password:
+
+```php
+request('GET', '/', ['cert' => ['/path/server.pem', 'password']]);
+```
+
+### connect_timeout
+
+By default, CodeIgniter does not impose a limit for cURL to attempt to connect to a website. If you need to modify this value, you can do so by passing the amount of time in seconds with the `connect_timeout` option. You can pass 0 to wait indefinitely:
+
+```php
+request('GET', 'http://example.com', ['connect_timeout' => 0]);
+```
+
+### cookie
+
+This specifies the filename that CURL should use to read cookie values from, and to save cookie values to. This is done using the `CURLOPT_COOKIEJAR` and `CURLOPT_COOKIEFILE` options. An example:
+
+```php
+request('GET', 'http://example.com', ['cookie' => WRITEPATH . 'CookieSaver.txt']);
+```
+
+### debug
+
+When `debug` is passed and set to `true`, this will enable additional debugging to echo to STDERR during the script execution.
+
+This is done by passing `CURLOPT_VERBOSE` and echoing the output. So, when you're running a built-in server via `spark serve`, you will see the output in the console. Otherwise, the output will be written to the server's error log.
+
+```php
+request('GET', 'http://example.com', ['debug' => true]);
+```
+
+You can pass a filename as the value for debug to have the output written to a file:
+
+```php
+request('GET', 'http://example.com', ['debug' => '/usr/local/curl_log.txt']);
+```
+
+### delay
+
+Allows you to pause for a number of milliseconds before sending the request:
+
+```php
+request('GET', 'http://example.com', ['delay' => 2000]);
+```
+
+### dns_cache_timeout
+
+!!! success "Available from version 4.7.0"
+
+By default, CodeIgniter does not change the DNS Cache Timeout value (`120` seconds). If you need to modify this value, you can do so by passing an amount of time in seconds with the `dns_cache_timeout` option.
+
+```php
+request('GET', '/', ['dns_cache_timeout' => 360]); // seconds
+```
+
+!!! note "Note"
+ Based on the [libcurl](https://curl.se/libcurl/c/CURLOPT_DNS_CACHE_TIMEOUT.html) documentation, you can set it to zero (`0`) to completely disable caching, or set to `-1` to make the cached entries remain forever.
+
+### form_params
+
+You can send form data in an application/x-www-form-urlencoded POST request by passing an associative array in the `form_params` option. This will set the `Content-Type` header to `application/x-www-form-urlencoded` if it's not already set:
+
+```php
+request('POST', '/post', [
+ 'form_params' => [
+ 'foo' => 'bar',
+ 'baz' => ['hi', 'there'],
+ ],
+]);
+```
+
+!!! note "Note"
+ `form_params` cannot be used with the [multipart](#multipart) option. You will need to use one or the other. Use `form_params` for `application/x-www-form-urlencoded` requests, and `multipart` for `multipart/form-data` requests.
+
+### fresh_connect
+
+!!! success "Available from version 4.7.0"
+
+By default, the request is sent using a fresh connection. You can disable this behavior using the `fresh_connect` option:
+
+```php
+request('GET', 'http://example.com', ['fresh_connect' => true]);
+$client->request('GET', 'http://example.com', ['fresh_connect' => false]);
+```
+
+### headers
+
+While you can set any headers this request needs by using the `setHeader()` method, you can also pass an associative array of headers in as an option. Each key is the name of a header, and each value is a string or array of strings representing the header field values:
+
+```php
+request('GET', '/', [
+ 'headers' => [
+ 'User-Agent' => 'testing/1.0',
+ 'Accept' => 'application/json',
+ 'X-Foo' => ['Bar', 'Baz'],
+ ],
+]);
+```
+
+If headers are passed into the constructor, they are treated as default values that will be overridden later by any further headers arrays or calls to `setHeader()`.
+
+### http_errors
+
+By default, CURLRequest will throw `HTTPException` if the HTTP code returned is greater than or equal to 400.
+
+If you want to see the response body, you can set `http_errors` to `false` to return the content instead:
+
+```php
+request('GET', '/status/500');
+// If the response code is 500, an HTTPException is thrown,
+// and a detailed error report is displayed if in development mode.
+
+$response = $client->request('GET', '/status/500', ['http_errors' => false]);
+echo $response->getStatusCode(); // 500
+echo $response->getBody(); // You can see the response body.
+```
+
+### json
+
+The `json` option is used to easily upload JSON-encoded data as the body of a request. A Content-Type header of `application/json` is added, overwriting any Content-Type that might already be set. The data provided to this option can be any value that `json_encode()` accepts:
+
+```php
+request('PUT', '/put', ['json' => ['foo' => 'bar']]);
+```
+
+!!! note "Note"
+ This option does not allow for any customization of the `json_encode()` function, or the Content-Type header. If you need that ability, you will need to encode the data manually, pass it through the `setBody()` method of CURLRequest, and set the Content-Type header with the `setHeader()` method.
+
+### multipart
+
+When you need to send files and other data via a POST request, you can use the `multipart` option, along with the [CURLFile Class](https://www.php.net/manual/en/class.curlfile.php).
+
+The values should be an associative array of POST data to send. For safer usage, the legacy method of uploading files by prefixing their name with an `@` has been disabled. Any files that you want to send must be passed as instances of CURLFile:
+
+```php
+request('POST', '/post', [
+ 'multipart' => [
+ 'foo' => 'bar',
+ 'userfile' => new \CURLFile('/path/to/file.txt'),
+ ],
+]);
+```
+
+!!! note "Note"
+ `multipart` cannot be used with the [form_params](#form_params) option. You can only use one or the other. Use `form_params` for `application/x-www-form-urlencoded` requests, and `multipart` for `multipart/form-data` requests.
+
+### proxy
+
+!!! success "Available from version 4.4.0"
+
+You can set a proxy by passing an associative array as the `proxy` option:
+
+```php
+request(
+ 'GET',
+ 'http://example.com',
+ ['proxy' => 'http://localhost:3128'],
+);
+```
+
+### query
+
+You can pass along data to send as query string variables by passing an associative array as the `query` option:
+
+```php
+request('GET', '/get', ['query' => ['foo' => 'bar']]);
+```
+
+### timeout
+
+By default, cURL functions are allowed to run as long as they take, with no time limit. You can modify this with the `timeout` option. The value should be the number of seconds you want the functions to execute for. Use 0 to wait indefinitely:
+
+```php
+request('GET', 'http://example.com', ['timeout' => 5]);
+```
+
+### user_agent
+
+Allows specifying the User Agent for requests:
+
+```php
+request('GET', 'http://example.com', ['user_agent' => 'CodeIgniter Framework v4']);
+```
+
+### verify
+
+This option describes the SSL certificate verification behavior. If the `verify` option is `true`, it enables the SSL certificate verification and uses the default CA bundle provided by the operating system. If set to `false`, it will disable the certificate verification (this is insecure, and allows man-in-the-middle attacks!). You can set it to a string that contains the path to a CA bundle to enable verification with a custom certificate. The default value is true:
+
+```php
+request('GET', '/', ['verify' => true]);
+
+// Use a custom SSL certificate on disk.
+$client->request('GET', '/', ['verify' => '/path/to/cert.pem']);
+
+// Disable validation entirely. (Insecure!)
+$client->request('GET', '/', ['verify' => false]);
+```
+
+### force_ip_resolve
+
+!!! success "Available from version 4.6.0"
+
+To set the HTTP handlers to use only the IPv4 protocol (`v4`) or the IPv6 protocol (`v6`):
+
+```php
+request('GET', '/', ['force_ip_resolve' => 'v4']); // v4 or v6
+```
+
+### version
+
+To set the HTTP protocol to use, you can pass a string or float with the version number (typically either `1.0` or `1.1`, with `2.0` supported since v4.3.0):
+
+```php
+request('GET', '/', ['version' => 1.0]);
+```
diff --git a/docs/libraries/email.md b/docs/libraries/email.md
new file mode 100644
index 000000000000..22a3de2cadb1
--- /dev/null
+++ b/docs/libraries/email.md
@@ -0,0 +1,489 @@
+# Email Class
+
+CodeIgniter's robust Email Class supports the following features:
+
+- Multiple Protocols: Mail, Sendmail, and SMTP
+- TLS and SSL Encryption for SMTP
+- Multiple recipients
+- CC and BCCs
+- HTML or Plaintext email
+- Attachments
+- Word wrapping
+- Priorities
+- BCC Batch Mode, enabling large email lists to be broken into small BCC batches.
+- Email Debugging tools
+
+## Using the Email Library
+
+### Sending Email
+
+Sending email is not only simple, but you can configure it on the fly or set your preferences in the **app/Config/Email.php** file.
+
+Here is a basic example demonstrating how you might send email:
+
+```php
+setFrom('your@example.com', 'Your Name');
+$email->setTo('someone@example.com');
+$email->setCC('another@another-example.com');
+$email->setBCC('them@their-example.com');
+
+$email->setSubject('Email Test');
+$email->setMessage('Testing the email class.');
+
+$email->send();
+```
+
+### Setting Email Preferences
+
+There are several preferences available to tailor how your email messages are sent. You can either set them manually as described here, or automatically via preferences stored in your config file, described in [Email Preferences](#email-preferences).
+
+#### Setting Email Preferences by Passing an Array
+
+Preferences are set by passing an array of preference values to the email initialize method. Here is an example of how you might set some preferences:
+
+```php
+initialize($config);
+```
+
+!!! note "Note"
+ Most of the preferences have default values that will be used if you do not set them.
+
+#### Setting Email Preferences in a Config File
+
+If you prefer not to set preferences using the above method, you can instead put them into the config file. Simply open the **app/Config/Email.php** file, and set your configs in the Email properties. Then save the file and it will be used automatically. You will NOT need to use the `$email->initialize()` method if you set your preferences in the config file.
+
+#### SSL versus TLS for SMTP Protocol
+
+To protect the username, password and email content while communicating with the SMTP server, encryption on the channel should be used. Two different standards are widely deployed and it is important to understand the differences when trying to troubleshoot email sending issues.
+
+Most SMTP servers allow connections on ports 465 or 587 when submitting emails. (The original port 25 is seldom used because many ISPs have blocking rules in place and because the communication is entirely in clear text.)
+
+The key difference is that port 465 expects the communication channel to be secured using TLS from the start as per [RFC 8314](https://tools.ietf.org/html/rfc8314). A connection to port 587 allows a clear-text connection and later will upgrade the channel to use encryption using the `STARTTLS` SMTP command.
+
+Upgrading a connection on port 465 may or may not be supported by the server, so the `STARTTLS` SMTP command may fail if the server does not allow it. If you set the port to 465, you should try to set `SMTPCrypto` to an empty string (`''`) since the communication is secured using TLS from the start and `STARTTLS` is not needed.
+
+If your configuration requires you to connect to port 587, you should most likely set `SMTPCrypto` to `tls` as this will implement the `STARTTLS` command while communicating with the SMTP server to switch from clear text to an encrypted channel. The initial communication will be made in clear text and the channel will be upgraded to TLS with the `STARTTLS` command.
+
+#### Reviewing Preferences
+
+The settings used for the last successful send are available from the instance property `$archive`. This is helpful for testing and debugging to determine the actual values at the time of the `send()` call.
+
+### Email Preferences
+
+The following is a list of all the preferences that can be set when sending email.
+
+| Preference | Default Value | Options | Description |
+|----|----|----|----|
+| **fromEmail** | | | The email address to be set in the "from" header. |
+| **fromName** | | | The name to be set in the "from" header. |
+| **userAgent** | CodeIgniter | | The "user agent". |
+| **protocol** | mail | `mail`, `sendmail`, or `smtp` | The mail sending protocol. |
+| **mailPath** | /usr/sbin/sendmail | | The server path to Sendmail. |
+| **SMTPHost** | | | SMTP Server Hostname. |
+| **SMTPAuthMethod** | login | `login`, `plain` | SMTP Authentication Method. (Available since 4.7.0) |
+| **SMTPUser** | | | SMTP Username. |
+| **SMTPPass** | | | SMTP Password. |
+| **SMTPPort** | 25 | | SMTP Port. (If set to `465`, TLS will be used for the connection regardless of the `SMTPCrypto` setting.) |
+| **SMTPTimeout** | 5 | | SMTP Timeout (in seconds). |
+| **SMTPKeepAlive** | false | `true`/`false` | Enable persistent SMTP connections. |
+| **SMTPCrypto** | tls | `tls`, `ssl`, or empty string (`''`) | SMTP Encryption. Setting this to `ssl` will create a secure channel to the server using SSL, and `tls` will issue a `STARTTLS` command to the server. Connections on port `465` should set this to an empty string (`''`). See also [SSL versus TLS for SMTP Protocol](#ssl-versus-tls-for-smtp-protocol). |
+| **wordWrap** | true | `true`/`false` | Enable word-wrap. |
+| **wrapChars** | 76 | | Character count to wrap at. |
+| **mailType** | text | `text` or `html` | Type of mail. If you send HTML email you must send it as a complete web page. Make sure you don't have any relative links or relative image paths, otherwise they will not work. |
+| **charset** | UTF-8 | | Character set (`utf-8`, `iso-8859-1`, etc.). |
+| **validate** | false | `true`/`false` | Whether to validate the email address. |
+| **priority** | 3 | 1, 2, 3, 4, 5 | Email Priority. `1` = highest. `5` = lowest. `3` = normal. |
+| **CRLF** | \r\n | `\r\n`, `\n` or `\r` | Newline character. (Use `\r\n` to comply with RFC 822). |
+| **newline** | \r\n | `\r\n`, `\n` or `\r` | Newline character. (Use `\r\n` to comply with RFC 822). |
+| **BCCBatchMode** | false | `true`/`false` | Enable BCC Batch Mode. |
+| **BCCBatchSize** | 200 | | Number of emails in each BCC batch. |
+| **DSN** | false | `true`/`false` | Enable notification messages from the server. |
+
+The defaults above reflect **app/Config/Email.php**, which is used by the Email service. If you instantiate `CodeIgniter\Email\Email` directly without a configuration object, its `$validate` property defaults to `true`.
+
+### Overriding Word Wrapping
+
+If you have word wrapping enabled (recommended to comply with RFC 822) and you have a very long link in your email, it can get wrapped too, causing it to become unclickable by the person receiving it. CodeIgniter lets you manually override word wrapping within part of your message like this:
+
+```
+The text of your email that
+gets wrapped normally.
+
+{unwrap}http://example.com/a_long_link_that_should_not_be_wrapped.html{/unwrap}
+
+More text that will be
+wrapped normally.
+```
+
+Place the item you do not want word-wrapped between: {unwrap} {/unwrap}
+
+## Class Reference
+
+## CodeIgniter\Email
+
+### Email
+
+#### setFrom($from[, $name = ''[, $returnPath = null]])
+
+- **Parameters**
+ - **$from** `string` "From" email address
+ - **$name** `string` "From" display name
+ - **$returnPath** `string` Optional email address to redirect undelivered email to
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Sets the email address and name of the person sending the email:
+
+```php
+setFrom('you@example.com', 'Your Name');
+```
+
+You can also set a Return-Path, to help redirect undelivered mail:
+
+```php
+setFrom('you@example.com', 'Your Name', 'returned_emails@example.com');
+```
+
+!!! note "Note"
+ Return-Path can't be used if you've configured 'smtp' as your protocol.
+
+#### setReplyTo($replyto[, $name = ''])
+
+- **Parameters**
+ - **$replyto** `string` Email address for replies
+ - **$name** `string` Display name for the reply-to email address
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Sets the reply-to address. If the information is not provided, the information in the `setFrom()` method is used. Example:
+
+```php
+setReplyTo('you@example.com', 'Your Name');
+```
+
+#### setTo($to)
+
+- **Parameters**
+ - **$to** `mixed` Comma-separated string or an array of email addresses
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Sets the email address(es) of the recipient(s). Can be a single email, a comma-separated list, or an array:
+
+```php
+setTo('someone@example.com');
+```
+
+```php
+setTo('one@example.com, two@example.com, three@example.com');
+```
+
+```php
+setTo(['one@example.com', 'two@example.com', 'three@example.com']);
+```
+
+#### setCC($cc)
+
+- **Parameters**
+ - **$cc** `mixed` Comma-separated string or an array of email addresses
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Sets the CC email address(es). Just like the "to" address, it can be a single email, a comma-separated list, or an array.
+
+#### setBCC($bcc[, $limit = ''])
+
+- **Parameters**
+ - **$bcc** `mixed` Comma-separated string or an array of email addresses
+ - **$limit** `int` Maximum number of emails to send per batch
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Sets the BCC email address(es). Just like the `setTo()` method, it can be a single email, a comma-separated list, or an array.
+
+If `$limit` is set, "batch mode" will be enabled, which will send the emails in batches, with each batch not exceeding the specified `$limit`.
+
+#### setSubject($subject)
+
+- **Parameters**
+ - **$subject** `string` Email subject line
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Sets the email subject:
+
+```php
+setSubject('This is my subject');
+```
+
+#### setMessage($body)
+
+- **Parameters**
+ - **$body** `string` Email message body
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Sets the email message body:
+
+```php
+setMessage('This is my message');
+```
+
+#### setAltMessage($str)
+
+- **Parameters**
+ - **$str** `string` Alternative email message body
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Sets the alternative email message body:
+
+```php
+setAltMessage('This is the alternative message');
+```
+
+This is an optional message string which can be used if you send HTML-formatted email. It lets you specify an alternative message with no HTML formatting which is added to the header string for people who do not accept HTML email. If you do not set your own message, CodeIgniter will extract the message from your HTML email and strip the tags.
+
+#### setHeader($header, $value)
+
+- **Parameters**
+ - **$header** `string` Header name
+ - **$value** `string` Header value
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Appends additional headers to the email:
+
+```php
+setHeader('Header1', 'Value1');
+$email->setHeader('Header2', 'Value2');
+```
+
+#### clear($clearAttachments = false)
+
+- **Parameters**
+ - **$clearAttachments** `bool` Whether or not to clear attachments
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Initializes all the email variables to an empty state. This method is intended for use if you run the email sending method in a loop, permitting the data to be reset between cycles.
+
+```php
+ $address) {
+ $email->clear();
+
+ $email->setTo($address);
+ $email->setFrom('your@example.com');
+ $email->setSubject('Here is your info ' . $name);
+ $email->setMessage('Hi ' . $name . ' Here is the info you requested.');
+ $email->send();
+}
+```
+
+If you set the parameter to true, any attachments will be cleared as well:
+
+```php
+clear(true);
+```
+
+#### send($autoClear = true)
+
+- **Parameters**
+ - **$autoClear** `bool` Whether to clear message data automatically
+
+- **Returns**: true on success, false on failure
+
+- **Return type**: `bool`
+
+The email sending method. Returns boolean true or false based on success or failure, enabling it to be used conditionally:
+
+```php
+send()) {
+ // Generate error
+}
+```
+
+This method will automatically clear all parameters if the request was successful. To stop this behaviour, pass false:
+
+```php
+send(false)) {
+ // Parameters won't be cleared
+}
+```
+
+!!! note "Note"
+ In order to use the `printDebugger()` method, you need to avoid clearing the email parameters.
+
+!!! note "Note"
+ If `BCCBatchMode` is enabled, and there are more than `BCCBatchSize` recipients, this method will always return boolean `true`.
+
+#### attach($filename[, $disposition = ''[, $newname = null[, $mime = '']]])
+
+- **Parameters**
+ - **$filename** `string` File name
+ - **$disposition** `string` 'disposition' of the attachment. Most email clients make their own decision regardless of the MIME specification used here.
+ - **$newname** `string` Custom file name to use in the email
+ - **$mime** `string` MIME type to use (useful for buffered data)
+
+- **Returns**: CodeIgniter\Email\Email instance (method chaining)
+
+- **Return type**: `CodeIgniter\Email\Email`
+
+Enables you to send an attachment. Put the file path/name in the first parameter. For multiple attachments, use the method multiple times. For example:
+
+```php
+attach('/path/to/photo1.jpg');
+$email->attach('/path/to/photo2.jpg');
+$email->attach('/path/to/photo3.jpg');
+```
+
+To use the default disposition (attachment), leave the second parameter blank. Otherwise, use a custom disposition:
+
+```php
+attach('image.jpg', 'inline');
+```
+
+You can also use a URL:
+
+```php
+attach('http://example.com/filename.pdf');
+```
+
+If you'd like to use a custom file name, you can use the third parameter:
+
+```php
+attach('filename.pdf', 'attachment', 'report.pdf');
+```
+
+If you need to use a buffer string instead of a real (physical) file, you can use the first parameter as the buffer, the third parameter as the file name, and the fourth parameter as the MIME type:
+
+```php
+attach($buffer, 'attachment', 'report.pdf', 'application/pdf');
+```
+
+#### setAttachmentCID($filename)
+
+- **Parameters**
+ - **$filename** `string` Existing attachment filename
+
+- **Returns**: Attachment Content-ID or false if not found
+
+- **Return type**: `string`
+
+Sets and returns an attachment's Content-ID, which enables you to embed an inline (picture) attachment into HTML. The first parameter must be the already attached file name.
+
+```php
+attach($filename);
+
+foreach ($list as $address) {
+ $email->setTo($address);
+ $cid = $email->setAttachmentCID($filename);
+ $email->setMessage(' ');
+ $email->send();
+}
+```
+
+!!! note "Note"
+ Content-ID for each email must be re-created for it to be unique.
+
+#### printDebugger($include = ['headers', 'subject', 'body'])
+
+- **Parameters**
+ - **$include** `array` Which parts of the message to print out
+
+- **Returns**: Formatted debug data
+
+- **Return type**: `string`
+
+Returns a string containing any server messages, the email headers, and the email message. Useful for debugging.
+
+You can optionally specify which parts of the message should be printed. Valid options are: **headers**, **subject**, **body**.
+
+Example:
+
+```php
+send(false);
+
+// Will only print the email headers, excluding the message subject and body
+$email->printDebugger(['headers']);
+```
+
+!!! note "Note"
+ By default, all of the raw data will be printed.
diff --git a/docs/libraries/encryption.md b/docs/libraries/encryption.md
new file mode 100644
index 000000000000..bcd624f70fe8
--- /dev/null
+++ b/docs/libraries/encryption.md
@@ -0,0 +1,419 @@
+# Encryption Service
+
+!!! important "Important"
+ DO NOT use this or any other *encryption* library for password storage! Passwords must be *hashed* instead, and you should do that through PHP's [Password Hashing extension](https://www.php.net/password).
+
+The Encryption Service provides two-way symmetric (secret key) data encryption. The service will instantiate and/or initialize an encryption **handler** to suit your parameters as explained below.
+
+Encryption Service handlers must implement CodeIgniter's simple `EncrypterInterface`. Using an appropriate PHP cryptographic extension or third-party library may require additional software to be installed on your server and/or might need to be explicitly enabled in your instance of PHP.
+
+The following PHP extensions are currently supported:
+
+- [OpenSSL](https://www.php.net/openssl)
+- [Sodium](https://www.php.net/manual/en/book.sodium)
+
+This is not a full cryptographic solution. If you need more capabilities, for example, public-key encryption, we suggest you consider direct use of OpenSSL or one of the other [Cryptography Extensions](https://www.php.net/manual/en/refs.crypto.php). A more comprehensive package like [Halite](https://github.com/paragonie/halite) (an O-O package built on libsodium) is another possibility.
+
+!!! note "Note"
+ Support for the `MCrypt` extension has been dropped. PHP deprecated it in 7.1 and removed it from the core in 7.2.
+
+## Using the Encryption Library
+
+Like all services in CodeIgniter, it can be loaded via `Config\Services`:
+
+```php
+encrypt($plainText);
+
+// Outputs: This is a plain-text message!
+echo $encrypter->decrypt($ciphertext);
+```
+
+And that's it! The Encryption library will do everything necessary for the whole process to be cryptographically secure out of the box. You don't need to worry about it.
+
+### Configuring the Library
+
+The example above uses the configuration settings found in **app/Config/Encryption.php**.
+
+| Option | Possible values (default in parentheses) |
+|----|----|
+| key | Encryption key starter |
+| driver | Preferred handler, e.g., OpenSSL or Sodium (`OpenSSL`) |
+| digest | Message digest algorithm (`SHA512`) |
+| blockSize | [**SodiumHandler** only] Padding length in bytes (`16`) |
+| cipher | [**OpenSSLHandler** only] Cipher to use (`AES-256-CTR`) |
+| encryptKeyInfo | [**OpenSSLHandler** only] Encryption key info (`''`) |
+| authKeyInfo | [**OpenSSLHandler** only] Authentication key info (`''`) |
+| rawData | [**OpenSSLHandler** only] Whether the cipher-text should be raw (`true`) |
+
+You can replace the config file's settings by passing a configuration object of your own to the `Services` call. The `$config` variable must be an instance of the `Config\Encryption` class.
+
+```php
+key = 'aBigsecret_ofAtleast32Characters';
+$config->driver = 'OpenSSL';
+
+$encrypter = service('encrypter', $config);
+```
+
+#### Configuration to Maintain Compatibility with CI3
+
+!!! success "Available from version 4.3.0"
+
+Since v4.3.0, you can decrypt data encrypted with CI3's Encryption. If you need to decrypt such data, use the following settings to maintain compatibility.
+
+```php
+driver = 'OpenSSL';
+
+// Your CI3's 'encryption_key'
+$config->key = hex2bin('64c70b0b8d45b80b9eba60b8b3c8a34d0193223d20fea46f8644b848bf7ce67f');
+// Your CI3's 'cipher' and 'mode'
+$config->cipher = 'AES-128-CBC';
+
+$config->rawData = false;
+$config->encryptKeyInfo = 'encryption';
+$config->authKeyInfo = 'authentication';
+
+$encrypter = service('encrypter', $config);
+```
+
+#### Supported HMAC Authentication Algorithms
+
+For HMAC message authentication, the Encryption library supports usage of the SHA-2 family of algorithms:
+
+| Algorithm | Raw length (bytes) | Hex-encoded length (bytes) |
+|-----------|--------------------|----------------------------|
+| SHA512 | 64 | 128 |
+| SHA384 | 48 | 96 |
+| SHA256 | 32 | 64 |
+| SHA224 | 28 | 56 |
+
+The reason for not including other popular algorithms, such as MD5 or SHA1, is that they are no longer considered secure enough and as such, we don't want to encourage their usage. If you absolutely need to use them, it is easy to do so via PHP's native [hash_hmac()](http://php.net/manual/en/function.hash-hmac.php) function.
+
+Stronger algorithms will, of course, be added in the future as they appear and become widely available.
+
+### Default Behavior
+
+By default, the Encryption Library uses the OpenSSL handler. That handler encrypts using the AES-256-CTR algorithm, your configured *key*, and SHA512 HMAC authentication.
+
+### Setting Your Encryption Key
+
+Your encryption key **must** be as long as the encryption algorithm in use allows. For AES-256, that's 256 bits or 32 bytes (characters) long.
+
+The key should be as random as possible, and it **must not** be a regular text string, nor the output of a hashing function, etc. To create a proper key, you can use the Encryption library's `createKey()` method.
+
+```php
+encrypt($plaintext));
+```
+
+#### Using Prefixes in Storing Keys
+
+You may take advantage of two special prefixes when storing your encryption keys: `hex2bin:` and `base64:`. When these prefixes immediately precede the value of your key, `Encryption` will intelligently parse the key and still pass a binary string to the library.
+
+```php
+';
+ // or
+ public $key = 'base64:';
+ // ...
+}
+```
+
+Similarly, you can use these prefixes in your **.env** file, too!
+
+```
+// For hex2bin
+encryption.key = hex2bin:
+
+// or
+encryption.key = base64:
+```
+
+### Encryption Key Rotation
+
+!!! success "Available from version 4.7.0"
+
+When you need to rotate your encryption key (for security best practices or compliance requirements), you can use the `previousKeys` configuration option to maintain the ability to decrypt data encrypted with old keys while using a new key for all new encryption operations.
+
+#### How It Works
+
+- **Encryption** always uses the current `key` value
+- **Decryption** tries the current `key` first
+- If decryption fails, it automatically falls back to trying each key in `previousKeys`
+- This allows seamless key rotation without data loss
+
+#### Configuration
+
+Add your old keys to the `$previousKeys` property in **app/Config/Encryption.php**:
+
+```php
+initialize($config);
+```
+
+Remember that `$config` must be an instance of the `Config\Encryption` class.
+
+## Class Reference
+
+## CodeIgniter\Encryption
+
+### Encryption
+
+#### createKey([$length = 32])
+
+- **Parameters**
+ - **$length** `int` Output length
+
+- **Returns**: A pseudo-random cryptographic key with the specified length, or `false` on failure
+
+- **Return type**: `string`
+
+Creates a cryptographic key by fetching random data from the operating system's sources (*i.e.* `/dev/urandom`).
+
+#### initialize([Encryption $config = null])
+
+- **Parameters**
+ - Config\Encryption `$config` Configuration parameters
+
+- **Returns**: `CodeIgniter\Encryption\EncrypterInterface` instance
+
+- **Return type**: `CodeIgniter\Encryption\EncrypterInterface`
+
+- **Throws**: `CodeIgniter\Encryption\Exceptions\EncryptionException`
+
+Initializes (configures) the library to use different settings.
+
+Example:
+
+```php
+initialize(['cipher' => 'AES-256-CTR']);
+```
+
+Please refer to the [Configuring the Library](#configuring-the-library) section for detailed info.
+
+### CodeIgniter\Encryption\EncrypterInterface
+
+#### encrypt($data[, $params = null])
+
+- **Parameters**
+ - **$data** `string` Data to encrypt
+ - **$params** `array|string|null` Configuration parameters (key)
+
+- **Returns**: Encrypted data
+
+- **Return type**: `string`
+
+- **Throws**: `CodeIgniter\Encryption\Exceptions\EncryptionException`
+
+Encrypts the input data and returns its ciphertext.
+
+If you pass parameters as the second argument, the `key` element will be used as the starting key for this operation if `$params` is an array. Alternatively, the starting key may be passed as a string.
+
+If you are using the SodiumHandler and want to pass a different `blockSize` at runtime, pass the `blockSize` key in the `$params` array.
+
+Examples:
+
+```php
+encrypt('My secret message');
+$ciphertext = $encrypter->encrypt('My secret message', ['key' => 'New secret key']);
+$ciphertext = $encrypter->encrypt('My secret message', ['key' => 'New secret key', 'blockSize' => 32]);
+$ciphertext = $encrypter->encrypt('My secret message', 'New secret key');
+$ciphertext = $encrypter->encrypt('My secret message', ['blockSize' => 32]);
+```
+
+#### decrypt($data[, $params = null])
+
+- **Parameters**
+ - **$data** `string` Data to decrypt
+ - **$params** `array|string|null` Configuration parameters (key)
+
+- **Returns**: Decrypted data
+
+- **Return type**: `string`
+
+- **Throws**: `CodeIgniter\Encryption\Exceptions\EncryptionException`
+
+Decrypts the input data and returns it in plain text.
+
+If you pass parameters as the second argument, the `key` element will be used as the starting key for this operation if `$params` is an array. Alternatively, the starting key may be passed as a string.
+
+If you are using the SodiumHandler and want to pass a different `blockSize` at runtime, pass the `blockSize` key in the `$params` array.
+
+Examples:
+
+```php
+decrypt($ciphertext);
+echo $encrypter->decrypt($ciphertext, ['key' => 'New secret key']);
+echo $encrypter->decrypt($ciphertext, ['key' => 'New secret key', 'blockSize' => 32]);
+echo $encrypter->decrypt($ciphertext, 'New secret key');
+echo $encrypter->decrypt($ciphertext, ['blockSize' => 32]);
+```
diff --git a/docs/libraries/file_collections.md b/docs/libraries/file_collections.md
new file mode 100644
index 000000000000..b4554b7c45b4
--- /dev/null
+++ b/docs/libraries/file_collections.md
@@ -0,0 +1,150 @@
+# File Collections
+
+Working with groups of files can be cumbersome, so the framework supplies the `FileCollection` class to facilitate locating and working with groups of files across the filesystem.
+
+## Basic Usage
+
+At its most basic, `FileCollection` is an array of files you set or build:
+
+```php
+addDirectory(APPPATH . 'Filters');
+```
+
+After you have input the files you would like to work with, you may remove files or use the filtering commands to remove or retain files matching a certain regex or glob-style pattern:
+
+```php
+removeFile(APPPATH . 'Filters/DevelopToolbar.php');
+
+$files->removePattern('#\.gitkeep#');
+$files->retainPattern('*.php');
+```
+
+When your collection is complete, you can use `get()` to retrieve the final list of file paths, or take advantage of `FileCollection` being countable and iterable to work directly with each `File`:
+
+```php
+get());
+echo 'I have ' . count($files) . ' files!';
+
+foreach ($files as $file) {
+ echo 'Moving ' . $file->getBasename() . ', ' . $file->getSizeByUnit('mb');
+ $file->move(WRITABLE . $file->getRandomName());
+}
+```
+
+Below are the specific methods for working with a `FileCollection`.
+
+## Starting a Collection
+
+### \__construct(string[] $files = [])
+
+The constructor accepts an optional array of file paths to use as the initial collection. These are passed to `add()`, so any files supplied by child classes in `$files` will remain.
+
+### define()
+
+Allows child classes to define their own initial files. This method is called by the constructor and allows predefined collections without having to use the collection methods.
+
+Example:
+
+```php
+add(APPPATH . 'Config', true)->retainPattern('*.php');
+ }
+}
+```
+
+Now you may use the `ConfigCollection` anywhere in your project to access all PHP files in **app/Config/** without having to re-call the collection methods every time.
+
+### set(array $files)
+
+Sets the list of input files to the provided string array of file paths. This will remove any existing files from the collection, so `$collection->set([])` is essentially a hard reset.
+
+## Inputting Files
+
+### add(string[]|string $paths, bool $recursive = true)
+
+Adds all files indicated by the path or array of paths. If the path resolves to a directory then `$recursive` will include sub-directories.
+
+### addFile(string $file) / addFiles(array $files)
+
+Adds the file or files to the current list of input files. Files are absolute paths to actual files.
+
+### removeFile(string $file) / removeFiles(array $files)
+
+Removes the file or files from the current list of input files.
+
+### addDirectory(string $directory, bool $recursive = false)
+
+### addDirectories(array $directories, bool $recursive = false)
+
+Adds all files from the directory or directories, optionally recursing into sub-directories. Directories are absolute paths to actual directories.
+
+## Filtering Files
+
+### removePattern(string $pattern, ?string $scope = null)
+
+### retainPattern(string $pattern, ?string $scope = null)
+
+Filters the current file list through the pattern (and optional scope), removing or retaining matched files.
+
+`$pattern` may be a complete regex (like `'#\A[A-Za-z]+\.php\z#'`) or a pseudo-regex similar to `glob()` (like `'*.css'`).
+
+If a `$scope` is provided then only files in or under that directory will be considered (i.e. files outside of `$scope` are always retained). When no scope is provided, all files are subject.
+
+Examples:
+
+```php
+add(APPPATH . 'Config', true); // Adds all Config files and directories
+
+$files->removePattern('*tion.php'); // Would remove Encryption.php, Validation.php, and boot/production.php
+$files->removePattern('*tion.php', APPPATH . 'Config/boot'); // Would only remove boot/production.php
+
+$files->retainPattern('#A.+php$#'); // Would keep only Autoload.php
+$files->retainPattern('#d.+php$#', APPPATH . 'Config/boot'); // Would keep everything but boot/production.php and boot/testing.php
+```
+
+### retainMultiplePatterns(array $patterns, ?string $scope = null)
+
+Provides the same functionality as `retainPattern()` but accepts an array of patterns to retain files from all patterns.
+
+Example:
+
+```php
+retainMultiplePatterns(['*.css', '*.js']); // Would keep only *.css and *.js files
+```
+
+## Retrieving Files
+
+### get(): string[]
+
+Returns an array of all the loaded input files.
+
+!!! note "Note"
+ `FileCollection` is an `IteratorAggregate` so you can work with it directly (e.g. `foreach ($collection as $file)`).
diff --git a/docs/libraries/files.md b/docs/libraries/files.md
new file mode 100644
index 000000000000..bd37077fa483
--- /dev/null
+++ b/docs/libraries/files.md
@@ -0,0 +1,142 @@
+# Working with Files
+
+CodeIgniter provides a File class that wraps the [SplFileInfo](https://www.php.net/manual/en/class.splfileinfo.php) class and provides some additional convenience methods. This class is the base class for [uploaded files](uploaded_files.md) and [images](images.md).
+
+## Getting a File instance
+
+You create a new File instance by passing in the path to the file in the constructor.
+
+```php
+$file = new \CodeIgniter\Files\File($path);
+```
+
+By default, the file does not need to exist. However, you can pass an additional argument of `true` to check that the file exists and throw `FileNotFoundException()` if it does not.
+
+## Taking Advantage of Spl
+
+Once you have an instance, you have the full power of the SplFileInfo class at the ready, including:
+
+```php
+// Get the file's basename
+echo $file->getBasename();
+// Get last modified time
+echo $file->getMTime();
+// Get the true real path
+echo $file->getRealPath();
+// Get the file permissions
+echo $file->getPerms();
+
+// Write CSV rows to it.
+if ($file->isWritable()) {
+ $csv = $file->openFile('w');
+
+ foreach ($rows as $row) {
+ $csv->fputcsv($row);
+ }
+}
+```
+
+## New Features
+
+In addition to all of the methods in the SplFileInfo class, you get some new tools.
+
+### getRandomName()
+
+You can generate a cryptographically secure random filename, with the current timestamp prepended, using the `getRandomName()` method. This is especially useful to rename files when moving them so that the filename is unguessable:
+
+```php
+// Generates something like: 1465965676_385e33f741.jpg
+$newName = $file->getRandomName();
+```
+
+### getSize()
+
+Returns the size of the file in bytes:
+
+```php
+$size = $file->getSize(); // 256901
+```
+
+A `RuntimeException` will be thrown if the file does not exist or an error occurs.
+
+### getSizeByUnit()
+
+!!! danger "Deprecated since version 4.6.0"
+
+Returns the size of the file, in bytes by default. You can pass in either `'kb'` or `'mb'` as the first parameter to get the results in kibibytes or mebibytes, respectively:
+
+```php
+$bytes = $file->getSizeByUnit(); // 256901
+$kilobytes = $file->getSizeByUnit('kb'); // 250.880
+$megabytes = $file->getSizeByUnit('mb'); // 0.245
+```
+
+A `RuntimeException` will be thrown if the file does not exist or an error occurs.
+
+### getSizeByBinaryUnit()
+
+!!! success "Available from version 4.6.0"
+
+Returns the size of the file, in bytes by default. You can pass in different FileSizeUnit values as the first parameter to get the results in kibibytes, mebibytes, etc., respectively. You can pass in a precision value as the second parameter to define the number of decimal places.
+
+```php
+$bytes = $file->getSizeByBinaryUnit(); // 256901
+$kibibytes = $file->getSizeByBinaryUnit(FileSizeUnit::KB); // 250.880
+$mebibytes = $file->getSizeByBinaryUnit(FileSizeUnit::MB); // 0.245
+```
+
+A `RuntimeException` will be thrown if the file does not exist or an error occurs.
+
+### getSizeByMetricUnit()
+
+!!! success "Available from version 4.6.0"
+
+Returns the size of the file, in bytes by default. You can pass in different FileSizeUnit values as the first parameter to get the results in kilobytes, megabytes, etc., respectively. You can pass in a precision value as the second parameter to define the number of decimal places.
+
+```php
+$bytes = $file->getSizeByMetricUnit(); // 256901
+$kilobytes = $file->getSizeByMetricUnit(FileSizeUnit::KB); // 256.901
+$megabytes = $file->getSizeByMetricUnit(FileSizeUnit::MB); // 0.256
+```
+
+A `RuntimeException` will be thrown if the file does not exist or an error occurs.
+
+### getMimeType()
+
+Retrieves the media type (mime type) of the file. Uses methods that are considered as secure as possible when determining the type of file:
+
+```php
+$type = $file->getMimeType();
+
+echo $type; // image/png
+```
+
+### guessExtension()
+
+Attempts to determine the file extension based on the trusted `getMimeType()` method. If the mime type is unknown, it will return null. This is often a more trusted source than simply using the extension provided by the filename. Uses the values in **app/Config/Mimes.php** to determine the extension:
+
+```php
+// Returns 'jpg' (WITHOUT the period)
+$ext = $file->guessExtension();
+```
+
+### Moving Files
+
+Each file can be moved to its new location with the aptly named `move()` method. This takes the directory to move the file to as the first parameter:
+
+```php
+$file->move(WRITEPATH . 'uploads');
+```
+
+By default, the original filename is used. You can specify a new filename by passing it as the second parameter:
+
+```php
+$newName = $file->getRandomName();
+$file->move(WRITEPATH . 'uploads', $newName);
+```
+
+The `move()` method returns a new File instance for the relocated file, so you must capture the result if the resulting location is needed:
+
+```php
+$file = $file->move(WRITEPATH . 'uploads');
+```
diff --git a/docs/libraries/honeypot.md b/docs/libraries/honeypot.md
new file mode 100644
index 000000000000..c39f7115edaf
--- /dev/null
+++ b/docs/libraries/honeypot.md
@@ -0,0 +1,48 @@
+# Honeypot Class
+
+The Honeypot Class makes it possible to determine when a Bot makes a request to a CodeIgniter4 application, if it's enabled in the **app/Config/Filters.php** file. This is done by attaching a form field to any form, and this field is hidden from a human but accessible to a Bot. When data is entered into the field, it's assumed the request is coming from a Bot. The bundled Honeypot filter throws a `HoneypotException` with an HTTP status code of 403 (Forbidden).
+
+## Enabling Honeypot
+
+To enable a Honeypot, changes have to be made to the **app/Config/Filters.php** file. Just uncomment honeypot from the `$globals` array, like:
+
+```php
+ [
+ 'honeypot',
+ // 'csrf',
+ // 'invalidchars',
+ ],
+ 'after' => [
+ 'toolbar',
+ 'honeypot',
+ // 'secureheaders',
+ ],
+ ];
+
+ // ...
+}
+```
+
+A sample Honeypot filter is bundled, as **system/Filters/Honeypot.php**. If it is not suitable, make your own at **app/Filters/Honeypot.php**, and modify the `$aliases` in **app/Config/Filters.php** appropriately.
+
+## Customizing Honeypot
+
+Honeypot can be customized. The fields below can be set either in **app/Config/Honeypot.php** or in **.env**.
+
+- `$hidden` - `true` or `false` to control visibility of the honeypot field; default is `true`
+- `$label` - HTML label for the honeypot field; default is `'Fill This Field'`
+- `$name` - name of the HTML form field used for the template; default is `'honeypot'`
+- `$template` - form field template used for the honeypot; default is `'{label} '`
+- `$container` - container tag for the template; default is `'{template}
'`. If you enable CSP, you can remove `style="display:none"`.
+- `$containerId` - [Since v4.3.0] this setting is used only when you enable CSP. You can change the id attribute for the container tag; default is `'hpc'`
diff --git a/docs/libraries/images.md b/docs/libraries/images.md
new file mode 100644
index 000000000000..3dfc298638c9
--- /dev/null
+++ b/docs/libraries/images.md
@@ -0,0 +1,370 @@
+# Image Manipulation Class
+
+CodeIgniter's Image Manipulation class lets you perform the following actions:
+
+- Image Resizing
+- Thumbnail Creation
+- Image Cropping
+- Image Rotating
+- Image Watermarking
+
+The following image libraries are supported: GD/GD2 and ImageMagick.
+
+## Initializing the Class
+
+Like most other classes in CodeIgniter, the image class is initialized in your controller by calling the global function `service()`:
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->fit(100, 100, 'center')
+ ->save('/path/to/image/mypic_thumb.jpg');
+```
+
+The above code tells the library to look for an image called **mypic.jpg** located in the **/path/to/image** folder, then create a new image from it that is 100 x 100 pixels, and save it to a new file **mypic_thumb.jpg**. Since it is using the `fit()` method, it will attempt to find the best portion of the image to crop based on the desired aspect ratio, and then crop and resize the result.
+
+An image can be processed through as many of the available methods as needed before saving. The original image is left untouched, and a new image is used and passed through each method, applying the results on top of the previous results:
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->reorient()
+ ->rotate(90)
+ ->crop(100, 100, 0, 0)
+ ->save('/path/to/image/mypic_thumb.jpg');
+```
+
+This example would take the same image and first fix any mobile phone orientation issues, rotate the image by 90 degrees, and then crop the result into a 100 x 100 pixel image, starting at the top left corner. The result would be saved as the thumbnail.
+
+!!! note "Note"
+ In order for the image class to be allowed to do any processing, the folder containing the image files must have write permissions.
+
+!!! note "Note"
+ Image processing can require a considerable amount of server memory for some operations. If you are experiencing out-of-memory errors while processing images, you may need to limit their maximum size, and/or adjust PHP memory limits.
+
+### Image Quality
+
+`save()` can take an additional parameter `$quality` to alter the resulting image quality. Values range from 0 to 100, with 90 being the framework default. This parameter only applies to JPEG and WebP images, and will be ignored otherwise:
+
+!!! note "Note"
+ The parameter `$quality` for WebP can be used since v4.4.0.
+
+!!! note "Note"
+ When using ImageMagick, the target path passed to `save()` determines the output format by its extension. ImageMagick may also interpret explicit format prefixes like `jpg:filename.png`. If the target path is based on user input, validate it against the formats your application allows, including any format prefix or extension.
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ // processing methods
+ ->save('/path/to/image/my_low_quality_pic.jpg', 10);
+```
+
+!!! note "Note"
+ Higher quality will result in larger file sizes. See also
+
+If you are only interested in changing the image quality without doing any processing, you will need to include the image resource, or you will end up with an exact copy:
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->withResource()
+ ->save('/path/to/image/my_low_quality_pic.jpg', 10);
+```
+
+## Processing Methods
+
+There are eight available processing methods:
+
+- `$image->crop()`
+- `$image->convert()`
+- `$image->fit()`
+- `$image->flatten()`
+- `$image->flip()`
+- `$image->resize()`
+- `$image->rotate()`
+- `$image->text()`
+
+These methods return the class instance so they can be chained together, as shown above. If they fail, they will throw a `CodeIgniter\Images\ImageException` that contains the error message. A good practice is to catch the exceptions, showing an error upon failure, like this:
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->fit(100, 100, 'center')
+ ->save('/path/to/image/mypic_thumb.jpg');
+} catch (\CodeIgniter\Images\Exceptions\ImageException $e) {
+ echo $e->getMessage();
+}
+```
+
+### Cropping Images
+
+Images can be cropped so that only a portion of the original image remains. This is often used when creating thumbnail images that should match a certain size/aspect ratio. This is handled with the `crop()` method:
+
+```
+crop(int $width = null, int $height = null, int $x = null, int $y = null, bool $maintainRatio = false, string $masterDim = 'auto')
+```
+
+- `$width` is the desired width of the resulting image, in pixels.
+- `$height` is the desired height of the resulting image, in pixels.
+- `$x` is the number of pixels from the left side of the image to start cropping.
+- `$y` is the number of pixels from the top of the image to start cropping.
+- `$maintainRatio` will, if true, adjust the final dimensions as needed to maintain the image's original aspect ratio.
+- `$masterDim` specifies which dimension should be left untouched when `$maintainRatio` is true. Values can be: `'width'`, `'height'`, or `'auto'`.
+
+To take a 50 x 50 pixel square out of the center of an image, you would need to first calculate the appropriate x and y offset values:
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->getFile()
+ ->getProperties(true);
+
+$xOffset = ($info['width'] / 2) - 25;
+$yOffset = ($info['height'] / 2) - 25;
+
+service('image', 'imagick')
+ ->withFile('/path/to/image/mypic.jpg')
+ ->crop(50, 50, $xOffset, $yOffset)
+ ->save('/path/to/new/image.jpg');
+```
+
+### Converting Images
+
+The `convert()` method changes the library's internal indicator for the desired file format. This doesn't touch the actual image resource, but indicates to `save()` what format to use:
+
+```
+convert(int $imageType)
+```
+
+- `$imageType` is one of PHP's image type constants (see for example ):
+
+ ```php
+ withFile('/path/to/image/mypic.jpg')
+ ->convert(IMAGETYPE_PNG)
+ ->save('/path/to/new/image.png');
+ ```
+
+!!! note "Note"
+ ImageMagick already saves files in the type indicated by their extension, ignoring `$imageType`.
+
+### Fitting Images
+
+The `fit()` method aims to help simplify cropping a portion of an image in a "smart" way, by doing the following steps:
+
+- Determine the correct portion of the original image to crop in order to maintain the desired aspect ratio.
+
+- Crop the original image.
+
+- Resize to the final dimensions.
+
+ ```
+ fit(int $width, int $height = null, string $position = 'center')
+ ```
+
+- `$width` is the desired final width of the image.
+
+- `$height` is the desired final height of the image.
+
+- `$position` determines the portion of the image to crop out. Allowed positions: `'top-left'`, `'top'`, `'top-right'`, `'left'`, `'center'`, `'right'`, `'bottom-left'`, `'bottom'`, `'bottom-right'`.
+
+This provides a much simpler way to crop that will always maintain the aspect ratio:
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->fit(100, 150, 'left')
+ ->save('/path/to/new/image.jpg');
+```
+
+### Flattening Images
+
+The `flatten()` method aims to add a background color behind transparent images (PNG) and convert RGBA pixels to RGB pixels.
+
+- Specify a background color when converting from transparent images to JPGs.
+
+ ```
+ flatten(int $red = 255, int $green = 255, int $blue = 255)
+ ```
+
+- `$red` is the red value of the background.
+
+- `$green` is the green value of the background.
+
+- `$blue` is the blue value of the background.
+
+```php
+withFile('/path/to/image/mypic.png')
+ ->flatten()
+ ->save('/path/to/new/image.jpg');
+
+service('image', 'imagick')
+ ->withFile('/path/to/image/mypic.png')
+ ->flatten(25, 25, 112)
+ ->save('/path/to/new/image.jpg');
+```
+
+### Flipping Images
+
+Images can be flipped along either their horizontal or vertical axis:
+
+```
+flip(string $dir)
+```
+
+- `$dir` specifies the axis to flip along. Can be either `'vertical'` or `'horizontal'`.
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->flip('horizontal')
+ ->save('/path/to/new/image.jpg');
+```
+
+### Resizing Images
+
+Images can be resized to fit any dimension you require with the `resize()` method:
+
+```
+resize(int $width, int $height, bool $maintainRatio = false, string $masterDim = 'auto')
+```
+
+- `$width` is the desired width of the new image in pixels.
+- `$height` is the desired height of the new image in pixels.
+- `$maintainRatio` determines whether the image is stretched to fit the new dimensions, or the original aspect ratio is maintained.
+- `$masterDim` specifies which axis should have its dimension honored when maintaining ratio. Use `'width'`, `'height'`, or `'auto'` to choose automatically.
+
+When resizing images, you can choose whether to maintain the ratio of the original image, or stretch/squash the new image to fit the desired dimensions. If `$maintainRatio` is true, the dimension specified by `$masterDim` will stay the same, while the other dimension will be altered to match the original image's aspect ratio.
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->resize(200, 100, true, 'height')
+ ->save('/path/to/new/image.jpg');
+```
+
+### Rotating Images
+
+The `rotate()` method allows you to rotate an image in 90-degree increments:
+
+```
+rotate(float $angle)
+```
+
+- `$angle` is the number of degrees to rotate. One of `90`, `180`, `270`.
+
+!!! note "Note"
+ While the `$angle` parameter accepts a float, it will convert it to an integer during the process. If the value is anything other than the three values listed above, it will throw a `CodeIgniter\Images\ImageException`.
+
+### Adding a Text Watermark
+
+You can overlay a text watermark onto the image very simply with the `text()` method. This is useful for placing copyright notices, photographer names, or simply marking the images as a preview so they won't be used in other people's final products.
+
+```
+text(string $text, array $options = [])
+```
+
+The first parameter is the string of text that you wish to display. The second parameter is an array of options that allow you to specify how the text should be displayed:
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->text('Copyright 2017 My Photo Co', [
+ 'color' => '#fff',
+ 'opacity' => 0.5,
+ 'withShadow' => true,
+ 'hAlign' => 'center',
+ 'vAlign' => 'bottom',
+ 'fontSize' => 20,
+ ])
+ ->save('/path/to/new/image.jpg');
+```
+
+The possible options that are recognized are as follows:
+
+- `color`: Text color (hex number), i.e., `'#ff0000'`
+- `opacity`: A number between `0` and `1` that represents the opacity of the text.
+- `withShadow`: Boolean value indicating whether to display a shadow.
+- `shadowColor`: Color of the shadow (hex number)
+- `shadowOffset`: How many pixels to offset the shadow. Applies to both the vertical and horizontal values.
+- `hAlign`: Horizontal alignment: `'left'`, `'center'`, `'right'`
+- `vAlign`: Vertical alignment: `'top'`, `'middle'`, `'bottom'`
+- `hOffset`: Additional offset on the x axis, in pixels
+- `vOffset`: Additional offset on the y axis, in pixels
+- `fontPath`: The full server path to the TTF font you wish to use. System font will be used if none is given.
+- `fontSize`: The font size to use. When using the GD handler with the system font, valid values are between `1` and `5`.
+
+### Clearing Image Metadata
+
+The `clearMetadata()` method removes metadata (EXIF, XMP, ICC, IPTC, comments, etc.) from an image.
+
+!!! important "Important"
+ The GD image library automatically strips all metadata during processing, so this method has no additional effect when using the GD handler. This behavior is built into GD itself and cannot be modified.
+
+Some essential technical metadata (dimensions, color depth) will be regenerated during save operations as they're required for image display. However, all privacy-sensitive information such as GPS location, camera details, and timestamps will be completely removed.
+
+```php
+withFile('/path/to/image/mypic.jpg')
+ ->clearMetadata()
+ ->save('/path/to/new/image.jpg');
+```
diff --git a/docs/libraries/index.md b/docs/libraries/index.md
new file mode 100644
index 000000000000..baca35276daf
--- /dev/null
+++ b/docs/libraries/index.md
@@ -0,0 +1,23 @@
+# Library Reference
+
+- [Caching Driver](caching.md)
+- [Cookies](cookies.md)
+- [Cross-Origin Resource Sharing (CORS)](cors.md)
+- [CURLRequest Class](curlrequest.md)
+- [Email Class](email.md)
+- [Encryption Service](encryption.md)
+- [Working with Files](files.md)
+- [File Collections](file_collections.md)
+- [Honeypot Class](honeypot.md)
+- [Image Manipulation Class](images.md)
+- [Pagination](pagination.md)
+- [Publisher](publisher.md)
+- [Security](security.md)
+- [Session Library](sessions.md)
+- [Throttler](throttler.md)
+- [Times and Dates](time.md)
+- [Typography](typography.md)
+- [Working with Uploaded Files](uploaded_files.md)
+- [Working with URIs](uri.md)
+- [User Agent Class](user_agent.md)
+- [Validation](validation.md)
diff --git a/docs/libraries/official_packages.md b/docs/libraries/official_packages.md
new file mode 100644
index 000000000000..047d3d2b6bad
--- /dev/null
+++ b/docs/libraries/official_packages.md
@@ -0,0 +1,38 @@
+# Official Packages
+
+The CodeIgniter framework cannot solve all of the problems that developers will encounter. Many users have said they love how small and fast the framework is, so we don't want to bloat the core framework. To bridge the gap, we are releasing official packages to provide additional functionality that not every site will need or want.
+
+## Shield
+
+[CodeIgniter Shield](https://shield.codeigniter.com/) is an authentication and authorization framework for CodeIgniter 4. It is designed to be secure, flexible, and easily extendable to meet the needs of many different types of websites. Among its many features are:
+
+- Session-based authentication
+- Personal access token authentication
+- Framework for after login/register "actions" (like Two Factor Authentication, etc.)
+- Role-Based Access Control with simple, flexible permissions
+- Per-user permission overrides
+- And more...
+
+## Settings
+
+[CodeIgniter Settings](https://settings.codeigniter.com) is a wrapper around the configuration files that allows any configuration setting to be saved to the database, while defaulting to the config files when no custom value has been stored. This allows an application to ship with the default config values, but adapt as the project grows or moves servers, without having to touch the code.
+
+## Tasks
+
+[CodeIgniter Tasks](https://tasks.codeigniter.com) is a simple task scheduler for CodeIgniter 4. It allows you to schedule tasks to run at specific times, or on a recurring basis. It is designed to be simple to use, but flexible enough to handle most use cases.
+
+## Queue
+
+[CodeIgniter Queue](https://queue.codeigniter.com) is a simple queue system for CodeIgniter 4. It allows you to queue up tasks to be run later.
+
+## Cache
+
+We provide a library with [PSR-6 and PSR-16 Cache Adapters](https://github.com/codeigniter4/cache) for CodeIgniter 4. This is not required for use, since CodeIgniter 4 comes with a fully functional cache component. This module is only for integrating third-party packages that rely on the PSR interface provisions.
+
+## DevKit
+
+[CodeIgniter DevKit](https://github.com/codeigniter4/devkit) provides all of the development tools that CodeIgniter uses to help ensure quality code, including our coding standard, static analysis tools and rules, unit testing, data generation, file-system mocking, security advisories, and more. This can be used in any of your personal projects or libraries to get you rapidly set up with 17 different tools.
+
+## Coding Standard
+
+The [CodeIgniter Coding Standard](https://github.com/CodeIgniter/coding-standard) holds the official coding standards of CodeIgniter based on PHP CS Fixer and powered by Nexus CS Config. This can be used in your own projects to form the basis of a consistent set of style rules that can be automatically applied to your code.
diff --git a/docs/libraries/pagination.md b/docs/libraries/pagination.md
new file mode 100644
index 000000000000..7b35c461dd7b
--- /dev/null
+++ b/docs/libraries/pagination.md
@@ -0,0 +1,605 @@
+# Pagination
+
+CodeIgniter provides a very simple but flexible pagination library that is simple to theme, works with the model, and is capable of supporting multiple paginators on a single page.
+
+## Loading the Library
+
+Like all services in CodeIgniter, it can be loaded via `Config\Services`, though you usually will not need to load it manually:
+
+```php
+ $model->paginate(10),
+ 'pager' => $model->pager,
+ ];
+
+ return view('users/index', $data);
+ }
+}
+```
+
+In this example, we first create a new instance of our `UserModel`. Then we populate the data to send to the view. The first element is the results from the database, **users**, which are retrieved for the correct page, returning 10 users per page. The second item that must be sent to the view is the Pager instance itself. As a convenience, the Model will hold on to the instance it used and store it in the public property, `$pager`. So, we grab that and assign it to the `$pager` variable in the view.
+
+### Customizing Query for Pagination
+
+To customize a query for pagination in a model, you can add [Query Builder](../database/query_builder.md) methods before the `paginate()` method.
+
+#### Adding WHERE
+
+If you want to add WHERE conditions, you can specify conditions directly:
+
+```php
+use App\Models\UserModel;
+
+// In your Controller.
+$model = model(UserModel::class);
+
+$data = [
+ 'users' => $model->where('ban', 1)->paginate(10),
+ 'pager' => $model->pager,
+];
+```
+
+You can move the conditions to a separate method:
+
+```php
+builder()->where('ban', 1);
+
+ return $this; // This will allow the call chain to be used.
+ }
+}
+```
+
+```php
+use App\Models\UserModel;
+
+// In your Controller.
+$model = model(UserModel::class);
+
+$data = [
+ 'users' => $model->banned()->paginate(10),
+ 'pager' => $model->pager,
+];
+```
+
+#### Adding JOIN
+
+You can join another table:
+
+```php
+builder()
+ ->select('news.*, category.name')
+ ->join('category', 'news.category_id = category.id');
+
+ return [
+ 'news' => $this->paginate($perPage),
+ 'pager' => $this->pager,
+ ];
+ }
+}
+```
+
+!!! important "Important"
+ It is important to understand that the `Model::paginate()` method uses the **Model** and the **Query Builder** instance in the Model. Therefore, trying to use `Model::paginate()` with [$db->query()](../database/queries.md#db-query) **will not work** because `$db->query()` executes the query immediately and is not associated with the Query Builder.
+
+If you need a complicated SQL query that you cannot write with Query Builder, try using [$db->query()](../database/queries.md#db-query) and [Manual Pagination](#manual-pagination).
+
+### Displaying Pager Links
+
+Within the view, we then need to tell it where to display the resulting links:
+
+```
+= $pager->links() ?>
+```
+
+And that's all it takes. The Pager class will render First and Last page links, as well as Next and Previous links for any pages more than two pages on either side of the current page.
+
+It is important to be aware that the library pattern for Next and Previous is different from what is used in the traditional way of paging results.
+
+Next and Previous here are linked to the group of links to be displayed in the pagination structure, and not to the next or previous page of records.
+
+If you prefer a simpler output, you can use the `simpleLinks()` method, which only uses "Older" and "Newer" links, instead of the detailed pagination links:
+
+```
+= $pager->simpleLinks() ?>
+```
+
+Behind the scenes, the library loads a view file that determines how the links are formatted, making it simple to modify to your needs. See below for details on how to completely customize the output.
+
+### Paginating Multiple Results
+
+If you need to provide links from two different result sets, you can pass group names to most of the pagination methods to keep the data separate:
+
+```php
+ $userModel->paginate(10, 'group1'),
+ 'pages' => $pageModel->paginate(15, 'group2'),
+ 'pager' => $userModel->pager,
+ ];
+
+ echo view('users/index', $data);
+ }
+}
+?>
+
+
+= $pager->links('group1') ?>
+= $pager->simpleLinks('group2') ?>
+```
+
+### Setting Page Manually
+
+If you need to specify which page of results to return, you can specify the page as the 3rd argument. This can be handy when you use a different way than the default `$_GET` variable to control which page to show.
+
+```php
+paginate(10, 'group1', $page);
+```
+
+### Specifying the URI Segment for Page
+
+It is also possible to use a URI segment for the page number, instead of the page query parameter. Simply specify the segment number to use as the fourth argument. URIs generated by the pager would then look like **https://domain.tld/foo/bar/[pageNumber]** instead of **https://domain.tld/foo/bar?page=[pageNumber]**.
+
+```php
+paginate(10, 'group1', null, $segment);
+```
+
+Please note: the `$segment` value cannot be greater than the number of URI segments plus 1.
+
+## Manual Pagination
+
+There may be times when you just need to create pagination based on known data. You can create links manually with the `makeLinks()` method, which takes the current page, the number of results per page, and the total number of items as the first, second, and third parameters, respectively:
+
+```php
+request->getGet('page') ?? 1);
+ $perPage = 20;
+ $total = 200;
+
+ // Call makeLinks() to make pagination links.
+ $pager_links = $pager->makeLinks($page, $perPage, $total);
+
+ $data = [
+ // ...
+ 'pager_links' => $pager_links,
+ ];
+
+ return view('users/index', $data);
+ }
+}
+?>
+
+
+= $pager_links ?>
+```
+
+This will, by default, display the links in the normal manner, as a series of links, but you can change the display template used by passing in the name of the template as the fourth parameter. More details can be found in the following sections:
+
+```
+$pager->makeLinks($page, $perPage, $total, 'template_name');
+```
+
+It is also possible to use a URI segment for the page number, instead of the page query parameter, as described in the previous section. Specify the segment number to use as the fifth parameter to `makeLinks()`:
+
+```
+$pager->makeLinks($page, $perPage, $total, 'template_name', $segment);
+```
+
+Please note: the `$segment` value cannot be greater than the number of URI segments plus 1.
+
+If you need to show many pagers on one page, then an additional parameter that defines a group can be helpful:
+
+```php
+setPath('path/for/my-group', 'my-group'); // Additionally you could define path for every group.
+$pager->makeLinks($page, $perPage, $total, 'template_name', $segment, 'my-group');
+```
+
+The Pagination library uses the **page** query parameter for HTTP queries by default (if no group or the `default` group name is given) or `page_[groupName]` for custom group names.
+
+## Paginating with Only Expected Queries
+
+By default, all GET queries are shown in the pagination links.
+
+For example, when accessing the URL **https://domain.tld?search=foo&order=asc&hello=i+am+here&page=2**, the page 3 link can be generated, along with the other links, as follows:
+
+```php
+links();
+// Page 3 link: https://domain.tld?search=foo&order=asc&hello=i+am+here&page=3
+```
+
+The `only()` method allows you to limit this just to queries already expected:
+
+```php
+only(['search', 'order'])->links();
+// Page 3 link: https://domain.tld?search=foo&order=asc&page=3
+```
+
+The *page* query is enabled by default. The `only()` method applies to all pagination links.
+
+## Customizing the Links
+
+### View Configuration
+
+When the links are rendered out to the page, they use a view file to describe the HTML. You can easily change the view that is used by editing **app/Config/Pager.php**:
+
+```php
+ 'CodeIgniter\Pager\Views\default_full',
+ 'default_simple' => 'CodeIgniter\Pager\Views\default_simple',
+ ];
+
+ // ...
+}
+```
+
+This setting stores the alias and [namespaced view paths](../outgoing/views.md) for the view that should be used. The `default_full` and `default_simple` views are used for the `links()` and `simpleLinks()` methods, respectively. To change the way those are displayed application-wide, you could assign a new view here.
+
+For example, say you create a new view file that works with the Foundation CSS framework, and you place that file at **app/Views/Pagers/foundation_full.php**. Since the **app** directory is namespaced as `App`, and all directories underneath it map directly to segments of the namespace, you can locate the view file through its namespace:
+
+```
+'default_full' => 'App\Views\Pagers\foundation_full'
+```
+
+Since it is under the standard **app/Views** directory, though, you do not need to namespace it since the `view()` method can locate it by filename. In that case, you can simply give the sub-directory and file name:
+
+```
+'default_full' => 'Pagers/foundation_full'
+```
+
+Once you have created the view and set it in the configuration, it will automatically be used. You don't have to replace the existing templates. You can create as many additional templates as you need in the configuration file. A common situation would be needing different styles for the frontend and the backend of your application.
+
+```php
+ 'CodeIgniter\Pager\Views\default_full',
+ 'default_simple' => 'CodeIgniter\Pager\Views\default_simple',
+ 'front_full' => 'App\Views\Pagers\foundation_full',
+ ];
+
+ // ...
+}
+```
+
+Once configured, you can specify it as the last parameter in the `links()`, `simpleLinks()`, and `makeLinks()` methods:
+
+```
+= $pager->links('group1', 'front_full') ?>
+= $pager->simpleLinks('group2', 'front_full') ?>
+= $pager->makeLinks($page, $perPage, $total, 'front_full') ?>
+```
+
+### Creating the View
+
+When you create a new view, you only need to create the code that is needed for creating the pagination links themselves. You should not create unnecessary wrapping divs, since the view might be used in multiple places and they would only limit its usefulness. It is easiest to demonstrate creating a new view by showing you the existing `default_full` template:
+
+```php
+setSurroundCount(2) ?>
+
+
+
+
+```
+
+#### setSurroundCount()
+
+In the first line, the `setSurroundCount()` method specifies that we want to show two links to either side of the current page link. The only parameter that it accepts is the number of links to show.
+
+!!! note "Note"
+ You must call this method first to generate correct pagination links.
+
+#### hasPrevious() & hasNext()
+
+These methods return a boolean `true` if there are more links that can be displayed on either side of the current page, based on the value passed to [setSurroundCount()](#setsurroundcount).
+
+For example, let's say we have 20 pages of data. The current page is page 3. If the surrounding count is 2, then the following links would show up like this:
+
+```
+1 | 2 | 3 | 4 | 5
+```
+
+Since the first link displayed is page one, `hasPrevious()` would return `false` since there is no page zero. However, `hasNext()` would return `true` since there are 15 additional pages of results after page five.
+
+#### getPrevious() & getNext()
+
+These methods return the **URL** for the previous or next pages of results on either side of the numbered links.
+
+For example, if you have the current page set at 5 and you want the links before and after (the surroundCount) to be 2 each, that will give you something like this:
+
+```
+3 | 4 | 5 | 6 | 7
+```
+
+`getPrevious()` returns the URL for page 2. `getNext()` returns the URL for page 8.
+
+If you want to get page 4 and page 6, use [getPreviousPage() & getNextPage()](#getpreviouspage-getnextpage) instead.
+
+#### getFirst() & getLast()
+
+Much like [getPrevious() & getNext()](#getprevious-getnext), these methods return the **URL** to the first and last pages in the result set.
+
+#### links()
+
+Returns an array of data about all of the numbered links. Each link's array contains the URI for the link, the title, which is just the number, and a boolean that tells whether the link is the current/active link or not:
+
+```php
+ false,
+ 'uri' => 'https://example.com/foo?page=2',
+ 'title' => 1,
+];
+```
+
+In the code presented for the standard pagination structure, the methods [getPrevious() & getNext()](#getprevious-getnext) are used to obtain the links to the previous and next pagination groups, respectively.
+
+If you want to use the pagination structure where prev and next will be links to the previous and next pages based on the current page, just replace the [getPrevious() & getNext()](#getprevious-getnext) methods with [getPreviousPage() & getNextPage()](#getpreviouspage-getnextpage), and the methods [hasPrevious() & hasNext()](#hasprevious-hasnext) with [hasPreviousPage() & hasNextPage()](#haspreviouspage-hasnextpage), respectively.
+
+See the following example with these changes:
+
+```php
+
+
+
+```
+
+#### hasPreviousPage() & hasNextPage()
+
+These methods return a boolean `true` if there are links to a page before and after, respectively, the current page being displayed.
+
+For example, let's say we have 20 pages of data. The current page is page 3. If the surrounding count is 2, then the following links would show up like this:
+
+```
+1 | 2 | 3 | 4 | 5
+```
+
+`hasPreviousPage()` would return `true` since page 2 exists, and `hasNextPage()` would return `true` since page 4 exists.
+
+!!! note "Note"
+ The difference from [hasPrevious() & hasNext()](#hasprevious-hasnext) is that these methods are based on the current page, while [hasPrevious() & hasNext()](#hasprevious-hasnext) are based on the set of links to be displayed before and after the current page based on the value passed in [setSurroundCount()](#setsurroundcount).
+
+#### getPreviousPage() & getNextPage()
+
+These methods return a **URL** for the previous and next pages in relation to the current page being displayed.
+
+For example, if you have the current page set at 5 and you want the links before and after (the surroundCount) to be 2 each, that will give you something like this:
+
+```
+3 | 4 | 5 | 6 | 7
+```
+
+`getPreviousPage()` returns the URL for page 4. `getNextPage()` returns the URL for page 6.
+
+!!! note "Note"
+ [getPrevious() & getNext()](#getprevious-getnext) return the URL for the previous or next pages of results on either side of the numbered links.
+
+If you want page numbers instead of URLs, you can use the following methods:
+
+#### getPreviousPageNumber() & getNextPageNumber()
+
+These methods return the page number for the previous or next pages in relation to the current page being displayed.
+
+#### getFirstPageNumber() & getLastPageNumber()
+
+These methods return the page numbers of the first and last pages in the set of links to be displayed. For example, if the set of links to be displayed is something like this:
+
+```
+3 | 4 | 5 | 6 | 7
+```
+
+`getFirstPageNumber()` will return 3 while `getLastPageNumber()` will return 7.
+
+!!! note "Note"
+ To obtain the page numbers of the first and last pages in the entire result set, you can use the following approach: The first page number is always 1, and [getPageCount()](#getpagecount) can be used to retrieve the last page number.
+
+#### getCurrentPageNumber()
+
+This method returns the page number of the current page.
+
+#### getPageCount()
+
+This method returns the total number of pages.
+
+### Displaying the Number of Items on the Page
+
+!!! success "Available from version 4.6.0"
+
+When paginating items, it’s often helpful to display the total number of items and the range of items shown on the current page. To simplify this task, new methods have been added. These methods make it easier to manage and display pagination details. Here's an example:
+
+```php
+setSurroundCount(1) ?>
+
+
+ Showing = $pager->getPerPageStart() ?>
+ to = $pager->getPerPageEnd() ?>
+ of = $pager->getTotal() ?> results
+
+```
+
+#### getTotal()
+
+Returns the total number of items.
+
+#### getPerPage()
+
+Returns the number of items to be displayed on the page.
+
+#### getPerPageStart()
+
+Returns the number of the item the page starts with.
+
+#### getPerPageEnd()
+
+Returns the number of the item the page ends with.
diff --git a/docs/libraries/publisher.md b/docs/libraries/publisher.md
new file mode 100644
index 000000000000..ac74687cf574
--- /dev/null
+++ b/docs/libraries/publisher.md
@@ -0,0 +1,504 @@
+# Publisher
+
+The Publisher library provides a means to copy files within a project using robust detection and error checking.
+
+## Loading the Library
+
+Because Publisher instances are specific to their source and destination, this library is not available through `Services` but should be instantiated or extended directly. E.g.:
+
+```php
+addPath('app/Config/Cookie.php');
+
+// You may also add from outside the source, but the files will not be merged into subdirectories
+$frameworkPublisher->addFiles([
+ '/opt/mail/susan',
+ '/opt/mail/ubuntu',
+]);
+$frameworkPublisher->addDirectory(SUPPORTPATH . 'Images');
+```
+
+Once all the files are staged, use one of the output commands (**copy()** or **merge()**) to process the staged files to their destination(s):
+
+```php
+copy();
+
+// Place all files into $destination, overwriting existing files
+$frameworkPublisher->copy(true);
+
+// Place files into their relative $destination directories, overwriting and saving the boolean result
+$result = $frameworkPublisher->merge(true);
+```
+
+See the [Library Reference](#library-reference) for a full description of available methods.
+
+### Automation and Discovery
+
+You may have regular publication tasks embedded as part of your application deployment or upkeep. `Publisher` leverages the powerful `Autoloader` to locate any child classes primed for publication:
+
+```php
+publish();
+
+ if ($result === false) {
+ CLI::error($publisher::class . ' failed to publish!', 'red');
+ }
+}
+```
+
+By default, `discover()` will search for the "Publishers" directory across all namespaces, but you may specify a different directory and it will return any child classes found:
+
+```php
+addPath('daily_photo.jpg')->copy(true); // `true` to enable overwrites
+ } catch (Throwable $e) {
+ $this->showError($e);
+ }
+ }
+}
+```
+
+Now running `spark publish:daily` will keep your homepage's image up to date. What if the photo is coming from an external API? You can use `addUri()` in place of `addPath()` to download the remote resource and publish it out instead:
+
+```php
+addUri('https://example.com/feeds/daily_photo.jpg')->copy(true);
+```
+
+### Asset Dependencies Example
+
+You want to integrate the frontend library "Bootstrap" into your project, but the frequent updates make it a hassle to keep up with. You can create a publication definition in your project to sync frontend assets by extending `Publisher` in your project. So **app/Publishers/BootstrapPublisher.php** might look like this:
+
+```php
+addPath('dist')
+
+ // Indicate we only want the minimized versions
+ ->retainPattern('*.min.*')
+
+ // Merge-and-replace to retain the original directory structure
+ ->merge(true);
+ }
+}
+```
+
+!!! note "Note"
+ The `$destination` directory must be created before executing the command.
+
+Now add the dependency via Composer and call `spark publish` to run the publication:
+
+```console
+composer require twbs/bootstrap
+php spark publish
+```
+
+... and you'll end up with something like this:
+
+```
+public/.htaccess
+public/favicon.ico
+public/index.php
+public/robots.txt
+public/
+ bootstrap/
+ css/
+ bootstrap.min.css
+ bootstrap-utilities.min.css.map
+ bootstrap-grid.min.css
+ bootstrap.rtl.min.css
+ bootstrap.min.css.map
+ bootstrap-reboot.min.css
+ bootstrap-utilities.min.css
+ bootstrap-reboot.rtl.min.css
+ bootstrap-grid.min.css.map
+ js/
+ bootstrap.esm.min.js
+ bootstrap.bundle.min.js.map
+ bootstrap.bundle.min.js
+ bootstrap.min.js
+ bootstrap.esm.min.js.map
+ bootstrap.min.js.map
+```
+
+### Module Deployment Example
+
+You want to give developers using your popular authentication module the ability to expand on the default behavior of your Migration, Controller, and Model. You can create your own module "publish" command to inject these components into an application for use:
+
+```php
+getNamespace('Math\\Auth')[0];
+
+ $publisher = new Publisher($source, APPPATH);
+
+ try {
+ // Add only the desired components
+ $publisher->addPaths([
+ 'Controllers',
+ 'Database/Migrations',
+ 'Models',
+ ])->merge(false); // Be careful not to overwrite anything
+ } catch (Throwable $e) {
+ $this->showError($e);
+
+ return;
+ }
+
+ // If publication succeeded then update namespaces
+ foreach ($publisher->getPublished() as $file) {
+ // Replace the namespace
+ $contents = file_get_contents($file);
+ $contents = str_replace('namespace Math\\Auth', 'namespace ' . APP_NAMESPACE, $contents);
+ file_put_contents($file, $contents);
+ }
+ }
+}
+```
+
+Now when your module users run `php spark auth:publish` they will have the following added to their project:
+
+```
+app/Controllers/AuthController.php
+app/Database/Migrations/2017-11-20-223112_create_auth_tables.php
+app/Models/LoginModel.php
+app/Models/UserModel.php
+```
+
+## Library Reference
+
+!!! note "Note"
+ `Publisher` is an extension of [FileCollection](file_collections.md), so it has access to all those methods for reading and filtering files.
+
+### Support Methods
+
+#### [static] discover(string $directory = 'Publishers', string $namespace = ''): Publisher[]
+
+Discovers and returns all Publishers in the specified namespace directory. For example, if both **app/Publishers/FrameworkPublisher.php** and **myModule/src/Publishers/AssetPublisher.php** exist and are extensions of `Publisher`, then `Publisher::discover()` would return an instance of each.
+
+#### publish(): bool
+
+Processes the full input-process-output chain. By default, this is the equivalent of calling `addPath($source)` and `merge(true)`, but child classes will typically provide their own implementation. `publish()` is called on all discovered Publishers when running `spark publish`. Returns success or failure.
+
+#### getScratch(): string
+
+Returns the temporary workspace, creating it if necessary. Some operations use intermediate storage to stage files and changes, and this provides the path to a transient, writable directory that you may use as well.
+
+#### getErrors(): array<string, Throwable>
+
+Returns any errors from the last write operation. The array keys are the files that caused the error, and the values are the Throwable that was caught. Use `getMessage()` on the Throwable to get the error message.
+
+#### addPath(string $path, bool $recursive = true)
+
+Adds all files indicated by the relative path. The path is a reference to actual files or directories relative to `$source`. If the relative path resolves to a directory then `$recursive` will include sub-directories.
+
+#### addPaths(array $paths, bool $recursive = true)
+
+Adds all files indicated by the relative paths. The paths are references to actual files or directories relative to `$source`. If a relative path resolves to a directory then `$recursive` will include sub-directories.
+
+#### addUri(string $uri)
+
+Downloads the contents of a URI using `CURLRequest` into the scratch workspace, then adds the resulting file to the list.
+
+#### addUris(array $uris)
+
+Downloads the contents of URIs using `CURLRequest` into the scratch workspace, then adds the resulting files to the list.
+
+!!! note "Note"
+ The CURL request made is a simple `GET` and uses the response body for the file contents. Some remote files may need a custom request to be handled properly.
+
+### Outputting Files
+
+#### wipe()
+
+Removes all files, directories, and sub-directories from `$destination`.
+
+!!! important "Important"
+ Use wisely.
+
+#### copy(bool $replace = true): bool
+
+Copies all files into the `$destination`. This does not recreate the directory structure, so every file from the current list will end up in the same destination directory. Using `$replace` will cause files to overwrite when there is already an existing file. Returns success or failure. Use `getPublished()` and `getErrors()` to troubleshoot failures. Be mindful of duplicate basename collisions, for example:
+
+```php
+addPaths([
+ 'pencil/lead.png',
+ 'metal/lead.png',
+]);
+
+// This is bad! Only one file will remain at /home/destination/lead.png
+$publisher->copy(true);
+```
+
+#### merge(bool $replace = true): bool
+
+Copies all files into the `$destination` in appropriate relative sub-directories. Any files that match `$source` will be placed into their equivalent directories in `$destination`, effectively creating a "mirror" or "rsync" operation. Using `$replace` will cause files to overwrite when there is already an existing file. Since directories are merged, this will not affect other files in the destination. Returns success or failure. Use `getPublished()` and `getErrors()` to troubleshoot failures.
+
+Example:
+
+```php
+addPaths([
+ 'pencil/lead.png',
+ 'metal/lead.png',
+]);
+
+// Results in "/home/destination/pencil/lead.png" and "/home/destination/metal/lead.png"
+$publisher->merge();
+```
+
+### Modifying Files
+
+#### replace(string $file, array $replaces): bool
+
+!!! success "Available from version 4.3.0"
+
+Replaces the `$file` contents. The second parameter `$replaces` array specifies the search strings as keys and the replacements as values.
+
+```php
+getNamespace('CodeIgniter\\Shield')[0];
+$publisher = new Publisher($source, APPPATH);
+
+$file = APPPATH . 'Config/Auth.php';
+
+$publisher->replace(
+ $file,
+ [
+ 'use CodeIgniter\Config\BaseConfig;' . "\n" => '',
+ 'class App extends BaseConfig' => 'class App extends \Some\Package\SomeConfig',
+ ],
+);
+```
+
+#### addLineAfter(string $file, string $line, string $after): bool
+
+!!! success "Available from version 4.3.0"
+
+Adds `$line` after a line with the specific string `$after`.
+
+```php
+getNamespace('CodeIgniter\\Shield')[0];
+$publisher = new Publisher($source, APPPATH);
+
+$file = APPPATH . 'Config/App.php';
+
+$publisher->addLineAfter(
+ $file,
+ ' public int $myOwnConfig = 1000;', // Adds this line
+ 'public bool $CSPEnabled = false;', // After this line
+);
+```
+
+#### addLineBefore(string $file, string $line, string $before): bool
+
+!!! success "Available from version 4.3.0"
+
+Adds `$line` before a line with the specific string `$before`.
+
+```php
+getNamespace('CodeIgniter\\Shield')[0];
+$publisher = new Publisher($source, APPPATH);
+
+$file = APPPATH . 'Config/App.php';
+
+$publisher->addLineBefore(
+ $file,
+ ' public int $myOwnConfig = 1000;', // Add this line
+ 'public bool $CSPEnabled = false;', // Before this line
+);
+```
diff --git a/docs/libraries/security.md b/docs/libraries/security.md
new file mode 100644
index 000000000000..d447854b71bb
--- /dev/null
+++ b/docs/libraries/security.md
@@ -0,0 +1,321 @@
+# Security
+
+The Security Class contains methods that help protect your site against Cross-Site Request Forgery attacks.
+
+## Loading the Library
+
+If your only interest in loading the library is to handle CSRF protection, then you will never need to load it, as it runs as a filter and has no manual interaction.
+
+If you find a case where you do need direct access, though, you may load it through the Services file:
+
+```php
+add()`, and use HTTP verbs in routes.
+2. Check the request method in the controller method before processing.
+
+E.g.:
+
+```
+if (! $this->request->is('post')) {
+ return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
+}
+```
+
+!!! note "Note"
+ The [$this->request->is()](../incoming/incomingrequest.md#is) method can be used since v4.3.0. In previous versions, you need to use `if (strtolower($this->request->getMethod()) !== 'post')`.
+
+#### When Auto-Routing is Enabled
+
+1. Check the request method in the controller method before processing.
+
+E.g.:
+
+```
+if (! $this->request->is('post')) {
+ return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
+}
+```
+
+### Config for CSRF
+
+#### CSRF Protection Methods
+
+!!! warning "Warning"
+ If you use [Session](sessions.md), be sure to use Session-based CSRF protection. Cookie-based CSRF protection will not prevent Same-site attacks. See [GHSA-5hm8-vh6r-2cjq](https://github.com/codeigniter4/shield/security/advisories/GHSA-5hm8-vh6r-2cjq) for details.
+
+By default, the Cookie-based CSRF Protection is used. It is the [Double Submit Cookie](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie) pattern on the OWASP Cross-Site Request Forgery Prevention Cheat Sheet.
+
+You can also use Session-based CSRF Protection. It is the [Synchronizer Token Pattern](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern).
+
+You can use the Session-based CSRF protection by editing the following config parameter value in **app/Config/Security.php**:
+
+```php
+getFlashdata('error') ?>
+```
+
+This provides a nicer experience than simply crashing.
+
+Even when the redirect value is `true`, AJAX calls will not redirect, but will throw a SecurityException.
+
+### Enable CSRF Protection
+
+You can enable CSRF protection by altering your **app/Config/Filters.php** and enabling the `csrf` filter globally:
+
+```php
+ [
+ // 'honeypot',
+ 'csrf',
+ ],
+ ];
+
+ // ...
+}
+```
+
+Select URIs can be whitelisted from CSRF protection (for example, API endpoints expecting externally POSTed content). You can do this by adding them as exceptions in the filter:
+
+```php
+ [
+ 'csrf' => ['except' => ['api/record/save']],
+ ],
+ ];
+
+ // ...
+}
+```
+
+Regular expressions are also supported (case-insensitive):
+
+```php
+ [
+ 'csrf' => ['except' => ['api/record/[0-9]+']],
+ ],
+ ];
+
+ // ...
+}
+```
+
+It is also possible to enable the CSRF filter only for specific methods:
+
+```php
+ ['csrf'],
+ 'POST' => ['csrf'],
+ ];
+
+ // ...
+}
+```
+
+!!! warning "Warning"
+ If you use `$methods` filters, you should [disable Auto Routing (Legacy)](../incoming/routing.md#use-defined-routes-only) because [Auto Routing (Legacy)](../incoming/routing.md#auto-routing-legacy) permits any HTTP method to access a controller. Accessing the controller with a method you don't expect could bypass the filter.
+
+### HTML Forms
+
+If you use the [form helper](../helpers/form_helper.md), then `form_open()` will automatically insert a hidden CSRF field in your forms.
+
+!!! note "Note"
+ To use auto-generation of the CSRF field, you need to turn the CSRF filter on for the form page. In most cases, it is requested using the `GET` method.
+
+If not, then you can use the always available `csrf_token()` and `csrf_hash()` functions:
+
+```
+
+```
+
+Additionally, you can use the `csrf_field()` method to generate this hidden input field for you:
+
+```
+// Generates:
+= csrf_field() ?>
+```
+
+When sending a JSON request, the CSRF token can also be passed as one of the parameters. The next way to pass the CSRF token is a special HTTP header whose name is available from the `csrf_header()` function.
+
+Additionally, you can use the `csrf_meta()` method to generate this handy meta tag for you:
+
+```
+// Generates:
+= csrf_meta() ?>
+```
+
+### The Order of Token Sent by Users
+
+The order of checking the availability of the CSRF token is as follows:
+
+1. `$_POST` array
+2. HTTP header
+3. `php://input` (JSON request) - bear in mind that this approach is the slowest one since we have to decode JSON and then re-encode it
+4. `php://input` (raw body) - for PUT, PATCH, and DELETE type of requests
+
+!!! note "Note"
+ `php://input` (raw body) is checked since v4.4.2.
+
+!!! note "Note"
+ If you include the CSRF token in a JSON object, removing it decodes and re-encodes the body. This changes its formatting and may affect number precision. To keep the JSON body unchanged, send the token in the `X-CSRF-TOKEN` header and leave the CSRF field out of the body.
+
+## Other Helpful Methods
+
+You will never need to use most of the methods in the Security class directly. The following are methods that you might find helpful that are not related to the CSRF protection.
+
+### sanitizeFilename()
+
+Tries to sanitize filenames in order to prevent directory traversal attempts and other security threats, which is particularly useful for files that were supplied via user input. The first parameter is the path to sanitize.
+
+If it is acceptable for the user input to include relative paths, e.g., **file/in/some/approved/folder.txt**, you can set the second optional parameter, `$relativePath`, to `true`.
+
+```php
+sanitizeFilename($request->getVar('filepath'));
+```
+
+This method is an alias for the `sanitize_filename()` function from the Security helper.
diff --git a/docs/libraries/sessions.md b/docs/libraries/sessions.md
new file mode 100644
index 000000000000..2783273726a0
--- /dev/null
+++ b/docs/libraries/sessions.md
@@ -0,0 +1,850 @@
+# Session Library
+
+The Session class permits you to maintain a user's "state" and track their activity while they browse your site.
+
+CodeIgniter comes with a few session storage drivers, which you can see in the last section of the table of contents:
+
+## Using the Session Class
+
+### Initializing a Session
+
+Sessions will typically run globally with each page load, so the Session class should be magically initialized.
+
+To access and initialize the session:
+
+```php
+close();
+```
+
+### What is Session Data?
+
+Session data is simply an array associated with a particular session ID (cookie).
+
+If you've used sessions in PHP before, you should be familiar with PHP's [$_SESSION superglobal](https://www.php.net/manual/en/reserved.variables.session.php) (if not, please read the content on that link).
+
+CodeIgniter gives access to its session data through the same means, as it uses the session handlers' mechanism provided by PHP. Using session data is as simple as manipulating (read, set and unset values) the `$_SESSION` array.
+
+!!! note "Note"
+ In general, it is bad practice to use global variables. So using the superglobal `$_SESSION` directly is not recommended.
+
+In addition, CodeIgniter provides 2 special types of session data that are further explained below: [Flashdata](#flashdata) and [Tempdata](#tempdata).
+
+!!! note "Note"
+ For historical reasons, we refer to session data excluding Flashdata and Tempdata as "userdata".
+
+### Retrieving Session Data
+
+Any piece of information from the session array is available through the `$_SESSION` superglobal. For example, to assign a previously stored `name` item to the `$name` variable, you will do this:
+
+```php
+get('name');
+```
+
+Or through the magic getter:
+
+```php
+name;
+```
+
+Or even through the session helper method:
+
+```php
+get();
+```
+
+!!! important "Important"
+ The `get()` method WILL return flashdata or tempdata items when retrieving a single item by key. It will not return flashdata or tempdata when grabbing all data from the session, however.
+
+### Adding Session Data
+
+Let's say a particular user logs into your site. Once authenticated, you could add their username and e-mail address to the session, making that data globally available to you without having to run a database query when you need it.
+
+You can simply assign data to the `$_SESSION` array, as with any other variable, or as a property of `$session`.
+
+You can pass an array containing your new session data to the `set()` method:
+
+```php
+set($array);
+```
+
+Here, `$array` is an associative array containing your new data. Here's an example:
+
+```php
+ 'johndoe',
+ 'email' => 'johndoe@some-site.com',
+ 'logged_in' => true,
+];
+
+$session->set($newdata);
+```
+
+If you want to add session data one value at a time, `set()` also supports this syntax:
+
+```php
+set('some_name', 'some_value');
+```
+
+If you want to verify that a session value exists, simply check with `isset()`:
+
+```php
+has('some_name');
+```
+
+### Pushing New Value to Session Data
+
+The `push()` method is used to push a new value onto a session value that is an array. For instance, if the `hobbies` key contains an array of hobbies, you can add a new value or replace the previous value in the array like so:
+
+```php
+ Array
+ * (
+ * [music] => rock
+ * [sport] => running
+ * )
+ */
+$session->set('hobbies', [
+ 'music' => 'rock',
+ 'sport' => 'running',
+]);
+
+/**
+ * [hobbies] => Array
+ * (
+ * [food] => cooking
+ * [music] => rock
+ * [sport] => tennis
+ * )
+ */
+$session->push('hobbies', [
+ 'food' => 'cooking',
+ 'sport' => 'tennis',
+]);
+```
+
+### Removing Session Data
+
+Just as with any other variable, unsetting a value in `$_SESSION` can be done through `unset()`:
+
+```php
+remove('some_name');
+```
+
+This method also accepts an array of item keys to unset:
+
+```php
+remove($array_items);
+```
+
+### Flashdata
+
+CodeIgniter supports "flashdata", or session data that will only be available for the next request, and is then automatically cleared.
+
+This can be very useful, especially for one-time informational, error or status messages (for example: "Record 2 deleted").
+
+It should be noted that flashdata variables are regular session variables, managed inside the CodeIgniter session handler.
+
+To mark an existing item as "flashdata":
+
+```php
+markAsFlashdata('item');
+```
+
+If you want to mark multiple items as flashdata, simply pass the keys as an array:
+
+```php
+markAsFlashdata(['item', 'item2']);
+```
+
+To add flashdata:
+
+```php
+markAsFlashdata('item');
+```
+
+Or alternatively, using the `setFlashdata()` method:
+
+```php
+setFlashdata('item', 'value');
+```
+
+You can also pass an array to `setFlashdata()`, in the same manner as `set()`.
+
+Reading flashdata variables is the same as reading regular session data through `$_SESSION`:
+
+```php
+getFlashdata('item');
+```
+
+!!! note "Note"
+ The `getFlashdata()` method returns null if the item cannot be found.
+
+Or to get an array with all flashdata, simply omit the key parameter:
+
+```php
+getFlashdata();
+```
+
+If you find that you need to preserve a flashdata variable through an additional request, you can do so using the `keepFlashdata()` method. You can either pass a single item or an array of flashdata items to keep.
+
+```php
+keepFlashdata('item');
+$session->keepFlashdata(['item1', 'item2', 'item3']);
+```
+
+### Tempdata
+
+CodeIgniter also supports "tempdata", or session data with a specific expiration time. After the value expires, or the session expires or is deleted, the value is automatically removed.
+
+Similarly to flashdata, tempdata variables are managed internally by the CodeIgniter session handler.
+
+To mark an existing item as "tempdata", simply pass its key and expiry time (in seconds!) to the `markAsTempdata()` method:
+
+```php
+markAsTempdata('item', 300);
+```
+
+You can mark multiple items as tempdata in two ways, depending on whether you want them all to have the same expiry time or not:
+
+```php
+markAsTempdata(['item', 'item2'], 300);
+
+// 'item' will be erased after 300 seconds, while 'item2'
+// will do so after only 240 seconds
+$session->markAsTempdata([
+ 'item' => 300,
+ 'item2' => 240,
+]);
+```
+
+To add tempdata:
+
+```php
+markAsTempdata('item', 300); // Expire in 5 minutes
+```
+
+Or alternatively, using the `setTempdata()` method:
+
+```php
+setTempdata('item', 'value', 300);
+```
+
+You can also pass an array to `setTempdata()`:
+
+```php
+ true, 'message' => 'Thanks for joining!'];
+$session->setTempdata($tempdata, null, $expire);
+```
+
+!!! note "Note"
+ If the expiration is omitted or set to 0, the default time-to-live value of 300 seconds (or 5 minutes) will be used.
+
+To read a tempdata variable, again you can just access it through the `$_SESSION` superglobal array:
+
+```php
+getTempdata('item');
+```
+
+!!! note "Note"
+ The `getTempdata()` method returns null if the item cannot be found.
+
+And of course, if you want to retrieve all existing tempdata:
+
+```php
+getTempdata();
+```
+
+If you need to remove a tempdata value before it expires, you can directly unset it from the `$_SESSION` array:
+
+```php
+removeTempdata('item');
+```
+
+### Changing the Session Key Type
+
+Since session data values like Flashdata and Tempdata are differentiated only by internal flags, you can change a value's type without rewriting its data.
+
+```php
+setFlashdata('alerts', 'Operation successful!');
+
+/*
+ * Get flash value 'Operation successful!' in another controller.
+ *
+ * echo session()->getFlashdata('alerts');
+ */
+
+// You can switch the session key type from Flashdata to Tempdata like this:
+session()->markAsTempdata('alerts');
+
+// Or simply rewrite it directly
+session()->setTempdata('alerts', 'Operation successful!');
+
+/*
+ * Get temp value 'Operation successful!' in another controller.
+ *
+ * echo session()->getTempdata('alerts');
+ *
+ * But flash value will be empty 'null'.
+ *
+ * echo session()->getFlashdata('alerts');
+ */
+```
+
+### Closing a Session
+
+#### close()
+
+!!! success "Available from version 4.4.0"
+
+To close the current session manually after you no longer need it, use the `close()` method:
+
+```php
+close();
+```
+
+You do not have to close the session manually. PHP will close it automatically after your script terminates. But as session data is locked to prevent concurrent writes, only one request may operate on a session at any time. You may improve your site performance by closing the session as soon as all changes to session data are done.
+
+This method will work in exactly the same way as PHP's [session_write_close()](https://www.php.net/session_write_close) function.
+
+### Destroying a Session
+
+#### destroy()
+
+To clear the current session (for example, during a logout), you may simply use the library's `destroy()` method:
+
+```php
+destroy();
+```
+
+This method will work in exactly the same way as PHP's [session_destroy()](https://www.php.net/session_destroy) function.
+
+This must be the last session-related operation that you do during the same request. All session data (including flashdata and tempdata) will be destroyed permanently.
+
+!!! note "Note"
+ You do not have to call this method from usual code. Clean up session data rather than destroying the session.
+
+### Accessing Session Metadata
+
+In CodeIgniter 2, the session data array included 4 items by default: 'session_id', 'ip_address', 'user_agent', 'last_activity'.
+
+This was due to the specifics of how sessions worked, but is now no longer necessary with our new implementation. However, it may happen that your application relied on these values, so here are alternative methods of accessing them:
+
+- session_id: `$session->session_id` or `session_id()` (PHP's built-in function)
+- ip_address: `$_SERVER['REMOTE_ADDR']`
+- user_agent: `$_SERVER['HTTP_USER_AGENT']` (unused by sessions)
+- last_activity: Depends on the storage, no straightforward way. Sorry!
+
+## Session Preferences
+
+CodeIgniter will usually make everything work out of the box. However, Sessions are a very sensitive component of any application, so some careful configuration must be done. Please take your time to consider all of the options and their effects.
+
+!!! note "Note"
+ Since v4.3.0, the new **app/Config/Session.php** has been added. Previously, the Session Preferences were in your **app/Config/App.php** file.
+
+You'll find the following Session-related preferences in your **app/Config/Session.php** file:
+
+| Preference | Default | Options | Description |
+|----|----|----|----|
+| **driver** | CodeIgniter\Session\Handlers\FileHandler | CodeIgniter\Session\Handlers\FileHandler CodeIgniter\Session\Handlers\DatabaseHandler CodeIgniter\Session\Handlers\MemcachedHandler CodeIgniter\Session\Handlers\RedisHandler CodeIgniter\Session\Handlers\ArrayHandler | The session storage driver to use. |
+| **cookieName** | ci_session | [A-Za-z_-] characters only | The name used for the session cookie. |
+| **expiration** | 7200 (2 hours) | Time in seconds (integer) | The number of seconds you would like the session to last. If you would like a non-expiring session (until browser is closed), set the value to zero: 0 |
+| **savePath** | WRITEPATH . 'session' | None | Specifies the storage location, which depends on the driver being used. |
+| **matchIP** | false | true/false (boolean) | Whether to validate the user's IP address when reading the session cookie. Note that some ISPs dynamically change the IP, so if you want a non-expiring session you will likely set this to false. |
+| **timeToUpdate** | 300 | Time in seconds (integer) | This option controls how often the session class will regenerate itself and create a new session ID. Setting it to 0 will disable session ID regeneration. |
+| **regenerateDestroy** | false | true/false (boolean) | 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. |
+
+!!! note "Note"
+ As a last resort, the Session library will try to fetch PHP's session-related INI settings, as well as CodeIgniter 3 settings such as 'sess_expire_on_close' when any of the above is not configured. However, you should never rely on this behavior as it can cause unexpected results or be changed in the future. Please configure everything properly.
+
+!!! note "Note"
+ If `expiration` is set to `0`, the `session.gc_maxlifetime` setting set by PHP in session management will be used as-is (often the default value of `1440`). This needs to be changed in `php.ini` or via `ini_set()` as needed.
+
+In addition to the values above, the Session cookie uses the following configuration values in your **app/Config/Cookie.php** file:
+
+| Preference | Default | Description |
+|----|----|----|
+| **domain** | '' | The domain for which the session is applicable |
+| **path** | / | The path to which the session is applicable |
+| **secure** | false | Whether to create the session cookie only on encrypted (HTTPS) connections |
+| **sameSite** | Lax | The SameSite setting for the session cookie |
+
+!!! note "Note"
+ The `httponly` setting (in **app/Config/Cookie.php**) doesn't have an effect on sessions. Instead, the HttpOnly parameter is always enabled, for security reasons. Additionally, the `Config\Cookie::$prefix` setting is completely ignored.
+
+## Session Drivers
+
+As already mentioned, the Session library comes with five handlers, or storage engines, that you can use:
+
+- CodeIgniter\Session\Handlers\FileHandler
+- CodeIgniter\Session\Handlers\DatabaseHandler
+- CodeIgniter\Session\Handlers\MemcachedHandler
+- CodeIgniter\Session\Handlers\RedisHandler
+- CodeIgniter\Session\Handlers\ArrayHandler
+
+By default, the `FileHandler` will be used when a session is initialized, because it is the safest choice and is expected to work everywhere (virtually every environment has a file system).
+
+However, any other driver may be selected via the `$driver` setting in your **app/Config/Session.php** file, if you choose to do so. Keep in mind, though, that every driver has different caveats, so be sure to familiarize yourself with them (below) before you make that choice.
+
+!!! note "Note"
+ The ArrayHandler is used during testing and stores all data within a PHP array, while preventing the data from being persisted. See [Testing Sessions](../testing/session_testing.md).
+
+### FileHandler Driver (the default)
+
+The 'FileHandler' driver uses your file system for storing session data.
+
+It can safely be said that it works exactly like PHP's own default session implementation, but in case this is an important detail for you, in fact it is not the same code and it has some limitations (and advantages).
+
+To be more specific, it doesn't support PHP's [directory level and mode formats used in session.save_path](https://www.php.net/manual/en/session.configuration.php#ini.session.save-path), and it has most of the options hard-coded for safety. Instead, only absolute paths are supported with the `$savePath` setting.
+
+Another important thing is to make sure that you don't use a publicly readable or shared directory for storing your session files. *Only you* should have access to the contents of your chosen *savePath* directory. Otherwise, anybody can see and steal session data, potentially enabling session hijacking.
+
+On UNIX-like operating systems, this is usually achieved by setting the 0700 mode permissions on that directory via the `chmod` command, which allows only the directory's owner to perform read and write operations on it. But be careful, because the system user *running* the script is usually not your own, but something like 'www-data' instead, so only setting those permissions will probably break your application.
+
+Instead, you should do something like this, depending on your environment:
+
+```console
+mkdir //writable/sessions/
+chmod 0700 //writable/sessions/
+chown www-data //writable/sessions/
+```
+
+The [built-in mechanism](https://www.php.net/manual/en/session.configuration.php#ini.session.gc-probability) for automatically clearing expired sessions may not run often, and you will notice that the *savePath* directory may be overflowing with files. To solve this problem, you will need to configure **cron** or **Task Scheduler** to delete outdated files.
+
+#### Bonus Tip
+
+Some of you will probably choose another session driver because file storage is usually slower. This is only half true.
+
+A very basic test will probably trick you into believing that an SQL database is faster, but in 99% of the cases, this is true only if you have a few current sessions. As the session count and server load increase - which is when it matters - the file system will consistently outperform almost all relational database setups.
+
+In addition, if performance is your only concern, you may want to look into using [tmpfs](https://eddmann.com/posts/storing-php-sessions-file-caches-in-memory-using-tmpfs/), which can make your sessions blazing fast.
+
+### DatabaseHandler Driver
+
+!!! important "Important"
+ Only MySQL and PostgreSQL databases are officially supported, due to lack of advisory locking mechanisms on other platforms. Using sessions without locks can cause all sorts of problems, especially with heavy usage of AJAX. Use the [close()](#close) method after you've finished processing session data if you're having performance issues.
+
+The 'DatabaseHandler' driver uses a relational database such as MySQL or PostgreSQL to store sessions. This is a popular choice among many users, because it allows the developer easy access to the session data within an application - it is just another table in your database.
+
+However, there is a restriction: You can NOT use a persistent connection.
+
+#### Configure DatabaseHandler
+
+##### Setting Table Name
+
+In order to use the 'DatabaseHandler' session driver, you must also create this table that we already mentioned and then set it as your `$savePath` value. For example, if you would like to use 'ci_sessions' as your table name, you would do this:
+
+```php
+
+
+!!! important "Important"
+ CodeIgniter's Session library does NOT use the actual 'redis' `session.save_handler`. Take note **only** of the path format in the link above.
+
+For the most common case, however, a simple `host:port` pair should be sufficient:
+
+```php
+check($name, 60, MINUTE);
+```
+
+Here we're using one of the [global constants](../general/common_functions.md) for the time, to make it a little more readable. This says that the bucket allows 60 actions every minute, or 1 action every second.
+
+Let's say that a third-party script was trying to hit a URL repeatedly. At first, it would be able to use all 60 of those tokens in less than a second. However, after that the Throttler would only allow one action per second, potentially slowing down the requests enough that the attack is no longer worth it.
+
+!!! note "Note"
+ For the Throttler class to work, the Cache library must be set up to use a handler other than dummy. For best performance, an in-memory cache, like Redis or Memcached, is recommended.
+
+## Rate Limiting
+
+The Throttler class does not do any rate limiting or request throttling on its own, but it is the key to making it work. An example [Filter](../incoming/filters.md) is provided that implements very simple rate limiting at one request per second per IP address. Here we will run through how it works, and how you could set it up and start using it in your application.
+
+### The Code
+
+You could make your own Throttler filter, at **app/Filters/Throttle.php**, along the lines of:
+
+```php
+|null $arguments
+ *
+ * @return ResponseInterface|void
+ */
+ public function before(RequestInterface $request, $arguments = null)
+ {
+ $throttler = service('throttler');
+
+ // Restrict an IP address to no more than 1 request
+ // per second across the entire site.
+ if ($throttler->check(md5($request->getIPAddress()), 60, MINUTE) === false) {
+ return service('response')->setStatusCode(429);
+ }
+ }
+
+ /**
+ * We don't have anything to do here.
+ *
+ * @param list|null $arguments
+ *
+ * @return void
+ */
+ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
+ {
+ // ...
+ }
+}
+```
+
+When run, this method first grabs an instance of the throttler. Next, it uses the IP address as the bucket name, and sets the limit to one request per second. If the throttler rejects the check, returning false, then we return a Response with the status code set to 429 - Too Many Requests, and the script execution ends before it ever hits the controller. This example will throttle based on a single IP address across all requests made to the site, not per page.
+
+### Applying the Filter
+
+We don't necessarily need to throttle every page on the site. For many web applications, it makes the most sense to apply this only to POST requests, though APIs might want to limit every request made by a user. In order to apply this to incoming requests, you need to edit **app/Config/Filters.php** and first add an alias to the filter:
+
+```php
+ \App\Filters\Throttle::class,
+ ];
+
+ // ...
+}
+```
+
+Next, we assign it to all POST requests made on the site:
+
+```php
+ ['throttle'],
+ ];
+
+ // ...
+}
+```
+
+!!! warning "Warning"
+ If you use `$methods` filters, you should [disable Auto Routing (Legacy)](../incoming/routing.md#use-defined-routes-only) because [Auto Routing (Legacy)](../incoming/routing.md#auto-routing-legacy) permits any HTTP method to access a controller. Accessing the controller with a method you don't expect could bypass the filter.
+
+And that's all there is to it. Now all POST requests made on the site will be rate limited.
+
+## Class Reference
+
+#### check(string $key, int $capacity, int $seconds[, int $cost = 1])
+
+- **Parameters**
+ - **$key** `string` The name of the bucket
+ - **$capacity** `int` The number of tokens the bucket holds
+ - **$seconds** `int` The number of seconds it takes for a bucket to completely fill
+ - **$cost** `int` The number of tokens that are spent on this action
+
+- **Returns**: true if the action can be performed, false if not
+
+- **Return type**: `bool`
+
+Checks to see if there are any tokens left within the bucket, or if too many have been used within the allotted time limit. During each check the available tokens are reduced by `$cost` if successful.
+
+#### getTokenTime()
+
+- **Returns**: The number of seconds until another token should be available.
+- **Return type**: `integer`
+
+After `check()` has been run and returned false, this method can be used to determine the time until a new token should be available and the action can be tried again. In this case, the minimum enforced wait time is one second.
+
+#### remove(string $key) : self
+
+- **Parameters**
+ - **$key** `string` The name of the bucket
+
+- **Returns**: `$this`
+
+- **Return type**: `self`
+
+Removes & resets the bucket. Won't fail if the bucket doesn't exist.
diff --git a/docs/libraries/time.md b/docs/libraries/time.md
new file mode 100644
index 000000000000..e0a7f7a3c964
--- /dev/null
+++ b/docs/libraries/time.md
@@ -0,0 +1,758 @@
+# Times and Dates
+
+CodeIgniter provides a fully localized, immutable date/time class that is built on PHP's DateTimeImmutable class, but uses the Intl extension's features to convert times across timezones and display the output correctly for different locales. This class is the `Time` class and lives in the `CodeIgniter\I18n` namespace.
+
+!!! note "Note"
+ Since the Time class extends `DateTimeImmutable`, if there are features that you need that this class doesn't provide, you can likely find them within the [DateTimeImmutable](https://www.php.net/manual/en/class.datetimeimmutable.php) class itself.
+
+!!! note "Note"
+ Prior to v4.3.0, the Time class extended `DateTime` and some inherited methods changed the current object state. The bug was fixed in v4.3.0. If you need the old Time class for backward compatibility, you can use the deprecated `TimeLegacy` class for the time being.
+
+## Instantiating
+
+There are several ways that a new Time instance can be created. The first is simply to create a new instance like any other class.
+
+When you do it this way, you can pass in a string representing the desired time. This can be any string that PHP's [DateTimeImmutable](https://www.php.net/manual/en/class.datetimeimmutable.php) constructor can parse. See [Supported Date and Time Formats](https://www.php.net/manual/en/datetime.formats.php) for details.
+
+```php
+toLocalizedString('MMM d, yyyy'); // March 9, 2016
+
+// Locale: fa
+$time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
+echo $time->toLocalizedString('MMM d, yyyy'); // مارس ۹, ۲۰۱۶
+```
+
+### toDateTimeString()
+
+This is the first of three helper methods to work with the [IntlDateFormatter](https://www.php.net/manual/en/class.intldateformatter.php) without having to remember their values. This will return a localized version of a string formatted as `Y-m-d H:i:s`:
+
+```php
+toDateTimeString(); // 2016-03-09 12:00:00
+
+// Locale: fa
+$time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
+echo $time->toDateTimeString(); // ۲۰۱۶-۰۳-۰۹ ۱۲:۰۰:۰۰
+```
+
+### toDateString()
+
+Displays just the localized date portion of the Time:
+
+```php
+toDateString(); // 2016-03-09
+
+// Locale: fa
+$time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
+echo $time->toDateString(); // ۲۰۱۶-۰۳-۰۹
+```
+
+### toTimeString()
+
+Displays just the localized time portion of the value:
+
+```php
+toTimeString(); // 12:00:00
+
+// Locale: fa
+$time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
+echo $time->toTimeString(); // ۱۲:۰۰:۰۰
+```
+
+### humanize()
+
+This method returns a string that displays the difference between the current date/time and the instance in a human-readable format that is geared towards being easily understood. It can create strings like '3 hours ago', 'in 1 month', etc.:
+
+```php
+humanize(); // 1 year ago
+```
+
+The exact time displayed is determined in the following manner:
+
+| Time difference | Result |
+|-------------------------------|--------------------------------|
+| 1 year < $time < 2 years | in 1 year / 1 year ago |
+| 1 month < $time < 1 year | in 6 months / 6 months ago |
+| 7 days < $time < 1 month | in 3 weeks / 3 weeks ago |
+| today < $time < 7 days | in 4 days / 4 days ago |
+| $time == yesterday / tomorrow | Yesterday / Tomorrow |
+| 59 minutes < $time < 1 day | in 2 hours / 2 hours ago |
+| now < $time < 1 hour | in 35 minutes / 35 minutes ago |
+| $time == now | Now |
+
+The result strings come from the language file, **system/Language/en/Time.php**. If you want to overwrite them, create **app/Language/{locale}/Time.php**.
+
+## Working with Individual Values
+
+The Time object provides a number of methods that allow you to get and set individual items, like the year, month, hour, etc., of an existing instance. All of the values retrieved through the following methods will be fully localized and respect the locale that the Time instance was created with.
+
+All of the following `getX()` and `setX()` methods can also be used as if they were class properties. So, methods like `getYear()` can also be accessed through `$time->year`, and so on.
+
+### Getters
+
+The following basic getters exist:
+
+```php
+getYear(); // '2016'
+echo $time->getMonth(); // '8'
+echo $time->getDay(); // '12'
+echo $time->getHour(); // '16'
+echo $time->getMinute(); // '15'
+echo $time->getSecond(); // '23'
+
+echo $time->year; // '2016'
+echo $time->month; // '8'
+echo $time->day; // '12'
+echo $time->hour; // '16'
+echo $time->minute; // '15'
+echo $time->second; // '23'
+```
+
+In addition to these, a number of methods exist to provide additional information about the date:
+
+```php
+getDayOfWeek(); // '6'
+echo $time->getDayOfYear(); // '225'
+echo $time->getWeekOfMonth(); // '2'
+echo $time->getWeekOfYear(); // '33'
+echo $time->getTimestamp(); // 1471018523 - UNIX timestamp (locale independent)
+echo $time->getQuarter(); // '3'
+
+echo $time->dayOfWeek; // '6'
+echo $time->dayOfYear; // '225'
+echo $time->weekOfMonth; // '2'
+echo $time->weekOfYear; // '33'
+echo $time->timestamp; // 1471018523
+echo $time->quarter; // '3'
+```
+
+#### getAge()
+
+Returns the age, in years, between the Time instance and the current time. Perfect for checking the age of someone based on their birthday:
+
+```php
+getAge(); // 5
+echo $time->age; // 5
+```
+
+#### getDST()
+
+Returns boolean true/false based on whether the Time instance is currently observing Daylight Saving Time:
+
+```php
+getDst(); // false
+echo Time::createFromDate(2012, 9, 1)->dst; // true
+```
+
+#### getLocal()
+
+Returns boolean true if the Time instance is in the same timezone as the application is currently running in:
+
+```php
+getLocal(); // true
+echo Time::now('Europe/London')->local; // false
+```
+
+#### getUtc()
+
+Returns boolean true if the Time instance is in UTC time:
+
+```php
+getUtc(); // false
+echo Time::now('UTC')->utc; // true
+```
+
+#### getTimezone()
+
+Returns a new [DateTimeZone](https://www.php.net/manual/en/class.datetimezone.php) object set to the timezone of the Time instance:
+
+```php
+getTimezone();
+$tz = Time::now()->timezone;
+
+echo $tz->getName();
+echo $tz->getOffset();
+```
+
+#### getTimezoneName()
+
+Returns the full [timezone string](https://www.php.net/manual/en/timezones.php) of the Time instance:
+
+```php
+getTimezoneName(); // America/Chicago
+echo Time::now('Europe/London')->timezoneName; // Europe/London
+```
+
+### Setters
+
+The following basic setters exist. If any of the values set are out of range, an `InvalidArgumentException` will be thrown.
+
+!!! note "Note"
+ All setters will return a new Time instance, leaving the original instance untouched.
+
+!!! note "Note"
+ All setters will throw an InvalidArgumentException if the value is out of range.
+
+```php
+setYear(2017);
+$time = $time->setMonth(4); // April
+$time = $time->setMonth('April');
+$time = $time->setMonth('Feb'); // February
+$time = $time->setDay(25);
+$time = $time->setHour(14); // 2:00 pm
+$time = $time->setMinute(30);
+$time = $time->setSecond(54);
+```
+
+#### setTimezone()
+
+Converts the time from its current timezone into the new one:
+
+```php
+setTimezone('Europe/London'); // Returns new instance converted to new timezone
+
+echo $time->getTimezoneName(); // American/Chicago
+echo $time2->getTimezoneName(); // Europe/London
+
+echo $time->toDateTimeString(); // 2020-05-13 10:00:00
+echo $time2->toDateTimeString(); // 2020-05-13 18:00:00
+```
+
+#### setTimestamp()
+
+Returns a new instance with the date set to the new timestamp:
+
+```php
+setTimestamp(strtotime('April 1, 2017'));
+
+echo $time->toDateTimeString(); // 2017-05-10 00:00:00
+echo $time2->toDateTimeString(); // 2017-04-01 00:00:00
+```
+
+!!! note "Note"
+ Prior to v4.6.0, due to a bug, this method might return an incorrect date/time. See [Upgrading Guide](../installation/upgrade_460.md#timesettimestamp-behavior-fix) for details.
+
+### Modifying the Value
+
+The following methods allow you to modify the date by adding values to or subtracting values from the current Time. This will not modify the existing Time instance, but will return a new instance.
+
+```php
+addSeconds(23);
+$time = $time->addMinutes(15);
+$time = $time->addHours(12);
+$time = $time->addDays(21);
+$time = $time->addMonths(14);
+$time = $time->addCalendarMonths(2);
+$time = $time->addYears(5);
+
+$time = $time->subSeconds(23);
+$time = $time->subMinutes(15);
+$time = $time->subHours(12);
+$time = $time->subDays(21);
+$time = $time->subMonths(14);
+$time = $time->subCalendarMonths(2);
+$time = $time->subYears(5);
+```
+
+#### addCalendarMonths() / subCalendarMonths()
+
+Modifies the current Time by adding or subtracting whole calendar months. These methods can be useful if you require that no calendar months be skipped in recurring dates. Refer to the table below for a comparison between `addMonths()` and `addCalendarMonths()` for an initial date of `2025-01-31`.
+
+| $months | addMonths() | addCalendarMonths() |
+|---------|-------------|---------------------|
+| 1 | 2025-03-03 | 2025-02-28 |
+| 2 | 2025-03-31 | 2025-03-31 |
+| 3 | 2025-05-01 | 2025-04-30 |
+| 4 | 2025-05-31 | 2025-05-31 |
+| 5 | 2025-07-01 | 2025-06-30 |
+| 6 | 2025-07-31 | 2025-07-31 |
+
+### Comparing Two Times
+
+The following methods allow you to compare one Time instance with another. All comparisons are first converted to UTC before comparisons are done, to ensure that different timezones respond correctly.
+
+#### equals()
+
+Determines if the datetime passed in is equal to the current instance. Equal in this case means that they represent the same moment in time, and are not required to be in the same timezone, as both times are converted to UTC and compared that way:
+
+```php
+equals($time2); // true
+```
+
+The value being tested against can be a Time instance, a DateTime instance, or a string with the full date time in a manner that a new DateTime instance can understand. When passing a string as the first parameter, you can pass a timezone string in as the second parameter. If no timezone is given, the system default will be used:
+
+```php
+equals('January 11, 2017 03:50:00', 'Europe/London'); // true
+```
+
+#### sameAs()
+
+This is identical to the `equals()` method, except that it only returns true when the date, time, AND timezone are all identical:
+
+```php
+sameAs($time2); // false
+$time2->sameAs('January 10, 2017 21:50:00', 'America/Chicago'); // true
+```
+
+#### isBefore()
+
+Checks if the current instance is before the passed-in time. The comparison is done against the UTC versions of both times:
+
+```php
+isBefore($time2); // true
+$time2->isBefore($time1); // false
+```
+
+The value being tested against can be a Time instance, a DateTime instance, or a string with the full date time in a manner that a new DateTime instance can understand. When passing a string as the first parameter, you can pass a timezone string in as the second parameter. If no timezone is given, the system default will be used:
+
+```php
+isBefore('March 15, 2013', 'America/Chicago'); // false
+```
+
+#### isAfter()
+
+Works exactly the same as `isBefore()`, except that it checks if the time is after the time passed in:
+
+```php
+isAfter($time2); // false
+$time2->isAfter($time1); // true
+```
+
+#### isPast()
+
+!!! success "Available from version 4.7.0"
+
+Determines if the current instance's time is in the past, relative to "now". It returns a boolean true/false:
+
+```php
+isPast(); // true
+```
+
+#### isFuture()
+
+!!! success "Available from version 4.7.0"
+
+Determines if the current instance's time is in the future, relative to "now". It returns a boolean true/false:
+
+```php
+isFuture(); // true
+```
+
+### Viewing Differences
+
+To compare two Times directly, you would use the `difference()` method, which returns a `CodeIgniter\I18n\TimeDifference` instance.
+
+The first parameter is either a Time instance, a DateTime instance, or a string with the date/time. If a string is passed in the first parameter, the second parameter can be a timezone string:
+
+```php
+difference(Time::now());
+$diff = $time->difference(new \DateTime('July 4, 1975', 'America/Chicago'));
+$diff = $time->difference('July 4, 1975 13:32:05', 'America/Chicago');
+```
+
+Once you have the TimeDifference instance, you have several methods you can use to find information about the difference between the two times. The value returned will be negative if it was in the past, or positive if in the future from the original time:
+
+```php
+difference($test);
+
+echo $diff->getYears(); // -7
+echo $diff->getMonths(); // -84
+echo $diff->getWeeks(); // -365
+echo $diff->getDays(); // -2557
+echo $diff->getHours(); // -61368
+echo $diff->getMinutes(); // -3682080
+echo $diff->getSeconds(); // -220924800
+```
+
+!!! note "Note"
+ Prior to v4.4.7, Time always converted the time zones to UTC before comparison. This could lead to unexpected results when the compared period contained a day that differed from 24 hours due to Daylight Saving Time (DST).
+
+ Starting with v4.4.7, when comparing date/times that are in the same time zone, the comparison is performed as is, without conversion to UTC.
+
+ ```php
+ difference($test);
+
+ echo $diff->getDays();
+ // 0 in v4.4.6 or before
+ // 1 in v4.4.7 or later
+ ```
+
+You can use either the `getX()` methods, or access the calculated values as if they were properties:
+
+```php
+years; // -7
+echo $diff->months; // -84
+echo $diff->weeks; // -365
+echo $diff->days; // -2557
+echo $diff->hours; // -61368
+echo $diff->minutes; // -3682080
+echo $diff->seconds; // -220924800
+```
+
+#### humanize()
+
+Much like Time's `humanize()` method, this returns a string that displays the difference between the times in a human-readable format that is geared towards being easily understood. It can create strings like '3 hours ago', 'in 1 month', etc. The biggest difference is in how very recent dates are handled:
+
+```php
+difference($test);
+
+echo $diff->humanize(); // 1 year ago
+```
+
+The exact time displayed is determined in the following manner:
+
+| Time difference | Result |
+|-----------------------------|--------------------------------|
+| 1 year < $time < 2 years | in 1 year / 1 year ago |
+| 1 month < $time < 1 year | in 6 months / 6 months ago |
+| 7 days < $time < 1 month | in 3 weeks / 3 weeks ago |
+| today < $time < 7 days | in 4 days / 4 days ago |
+| 1 hour < $time < 1 day | in 8 hours / 8 hours ago |
+| 1 minute < $time < 1 hour | in 35 minutes / 35 minutes ago |
+| $time < 1 minute | Now |
+
+The result strings come from the language file, **system/Language/en/Time.php**. If you want to overwrite them, create **app/Language/{locale}/Time.php**.
diff --git a/docs/libraries/typography.md b/docs/libraries/typography.md
new file mode 100644
index 000000000000..739a29c2b126
--- /dev/null
+++ b/docs/libraries/typography.md
@@ -0,0 +1,78 @@
+# Typography
+
+The Typography library contains methods that help you format text in semantically relevant ways.
+
+## Loading the Library
+
+Like all services in CodeIgniter, it can be loaded via `Config\Services`, though you usually will not need to load it manually:
+
+```php
+autoTypography($string);
+```
+
+!!! note "Note"
+ Typographic formatting can be processor-intensive, particularly if you have a lot of content being formatted. If you choose to use this function, you may want to consider [caching](../general/caching.md) your pages.
+
+#### formatCharacters($str)
+
+- **Parameters**
+ - **$str** `string` Input string
+
+- **Returns**: String with formatted characters.
+
+- **Return type**: `string`
+
+This function mainly converts double and single quotes to curly entities, but it also converts em-dashes, double spaces, and ampersands.
+
+Usage example:
+
+```php
+formatCharacters($string);
+```
+
+#### nl2brExceptPre($str)
+
+- **Parameters**
+ - **$str** `string` Input string
+
+- **Returns**: String with HTML-formatted line breaks
+
+- **Return type**: `string`
+
+Converts newlines to ` ` tags unless they appear within `` tags. This function is identical to the native PHP `nl2br()` function, except that it ignores `` tags.
+
+Usage example:
+
+```php
+nl2brExceptPre($string);
+```
diff --git a/docs/libraries/uploaded_files.md b/docs/libraries/uploaded_files.md
new file mode 100644
index 000000000000..599ba6de3853
--- /dev/null
+++ b/docs/libraries/uploaded_files.md
@@ -0,0 +1,539 @@
+# Working with Uploaded Files
+
+CodeIgniter makes working with files uploaded through a form much simpler and more secure than using PHP's `$_FILES` array directly. The UploadedFile class extends the [File class](files.md) and thus gains all of the features of that class.
+
+!!! note "Note"
+ This is not the same as the File Uploading class in CodeIgniter 3. This provides a raw interface to the uploaded files with a few small features.
+
+## File Upload Form Tutorial
+
+Uploading a file involves the following general process:
+
+- An upload form is displayed, allowing a user to select a file and upload it.
+- When the form is submitted, the file is uploaded to the destination you specify.
+- Along the way, the file is validated to make sure it is allowed to be uploaded based on the preferences you set.
+- Once uploaded, the user will be shown a success message.
+
+To demonstrate this process, here is a brief tutorial. Afterward, you'll find reference information.
+
+### Creating the Upload Form
+
+Using a text editor, create a form called **upload_form.php**. In it, place this code and save it to your **app/Views** directory:
+
+```php
+
+
+
+ Upload Form
+
+
+
+
+ = esc($error) ?>
+
+
+= form_open_multipart('upload/upload') ?>
+
+
+
+
+
+
+
+```
+
+You'll notice we are using a form helper to create the opening form tag. File uploads require a multipart form, so the helper creates the proper syntax for you.
+
+You'll also notice we have an `$errors` variable. This is so we can show error messages in the event the user does something wrong.
+
+### The Success Page
+
+Using a text editor, create a form called **upload_success.php**. In it, place this code and save it to your **app/Views** directory:
+
+```
+
+
+
+ Upload Form
+
+
+
+Your file was successfully uploaded!
+
+
+ name: = esc($uploaded_fileinfo->getBasename()) ?>
+ size: = esc($uploaded_fileinfo->getSizeByBinaryUnit(\CodeIgniter\Files\FileSizeUnit::KB)) ?> KB
+ extension: = esc($uploaded_fileinfo->guessExtension()) ?>
+
+
+= anchor('upload', 'Upload Another File!') ?>
+
+
+
+```
+
+### The Controller
+
+Using a text editor, create a controller called **Upload.php**. In it, place this code and save it to your **app/Controllers** directory:
+
+```php
+ []]);
+ }
+
+ public function upload()
+ {
+ $validationRule = [
+ 'userfile' => [
+ 'label' => 'Image File',
+ 'rules' => [
+ 'uploaded[userfile]',
+ 'is_image[userfile]',
+ 'mime_in[userfile,image/jpg,image/jpeg,image/gif,image/png,image/webp]',
+ 'max_size[userfile,100]',
+ 'max_dims[userfile,1024,768]',
+ ],
+ ],
+ ];
+ if (! $this->validateData([], $validationRule)) {
+ $data = ['errors' => $this->validator->getErrors()];
+
+ return view('upload_form', $data);
+ }
+
+ $img = $this->request->getFile('userfile');
+
+ if (! $img->hasMoved()) {
+ $filepath = WRITEPATH . 'uploads/' . $img->store();
+
+ $data = ['uploaded_fileinfo' => new File($filepath)];
+
+ return view('upload_success', $data);
+ }
+
+ $data = ['errors' => 'The file has already been moved.'];
+
+ return view('upload_form', $data);
+ }
+}
+```
+
+Only the [Rules for File Uploads](validation.md#rules-for-file-uploads) can be used to validate uploaded files.
+
+Therefore, the rule `required` cannot be used either, so if the file is required, use the rule `uploaded` instead.
+
+Note that an empty array (`[]`) is passed as the first argument to `$this->validateData()`. This is because the file validation rules get the data for the uploaded file directly from the Request object.
+
+If the form has fields other than the file upload, pass the field data as the first argument.
+
+### The Routes
+
+Using a text editor, open **app/Config/Routes.php**. In it, add the following two routes:
+
+```php
+get('/', 'Home::index');
+
+$routes->get('upload', 'Upload::index'); // Add this line.
+$routes->post('upload/upload', 'Upload::upload'); // Add this line.
+
+// ...
+```
+
+### The Upload Directory
+
+The uploaded files are stored in the **writable/uploads/** directory.
+
+### Try it!
+
+To try your form, visit your site using a URL similar to this one:
+
+```
+example.com/index.php/upload/
+```
+
+You should see an upload form. Try uploading an image file (either a **jpg**, **gif**, **png**, or **webp**). If the path in your controller is correct, it should work.
+
+## Accessing Files
+
+### All Files
+
+When you upload files, they can be accessed natively in PHP through the `$_FILES` superglobal. This array has some major shortcomings when working with multiple files uploaded at once, and has potential security flaws many developers are not aware of. CodeIgniter helps with both of these situations by standardizing your usage of files behind a common interface.
+
+Files are accessed through the current `IncomingRequest` instance. To retrieve all files that were uploaded with this request, use `getFiles()`. This will return an array of files represented by instances of `CodeIgniter\HTTP\Files\UploadedFile`:
+
+```php
+request->getFiles();
+```
+
+Of course, there are multiple ways to name the file input, and anything but the simplest can create strange results. The array is returned in a manner that you would expect. With the simplest usage, a single file might be submitted like:
+
+```
+
+```
+
+This would return a simple array like:
+
+```
+[
+ 'avatar' => // UploadedFile instance,
+];
+```
+
+!!! note "Note"
+ The UploadedFile instance corresponds to `$_FILES`. Even if a user just clicks the submit button and does not upload any file, the instance will still exist. You can check that the file was actually uploaded with the `isValid()` method in UploadedFile. See [Verify a File](#verify-a-file).
+
+If you used array notation for the name, the input would look something like:
+
+```
+
+```
+
+The array returned by `getFiles()` would look more like this:
+
+```
+[
+ 'my-form' => [
+ 'details' => [
+ 'avatar' => // UploadedFile instance
+ ],
+ ],
+]
+```
+
+In some cases, you may specify an array of files to upload:
+
+```
+Upload an avatar:
+Upload an avatar:
+```
+
+In this case, the returned array of files would be more like:
+
+```
+[
+ 'my-form' => [
+ 'details' => [
+ 'avatars' => [
+ 0 => // UploadedFile instance,
+ 1 => // UploadedFile instance,
+ ],
+ ],
+ ],
+]
+```
+
+### Single File
+
+If you just need to access a single file, you can use `getFile()` to retrieve the file instance directly. This will return an instance of `CodeIgniter\HTTP\Files\UploadedFile`.
+
+#### Simplest usage
+
+With the simplest usage, a single file might be submitted like:
+
+```
+
+```
+
+This would return a simple file instance like:
+
+```php
+request->getFile('userfile');
+```
+
+#### Array notation
+
+If you used array notation for the name, the input would look something like:
+
+```
+
+```
+
+To get the file instance:
+
+```php
+request->getFile('my-form.details.avatar');
+```
+
+### Multiple files
+
+```
+
+```
+
+In the controller:
+
+```php
+request->getFiles()) {
+ foreach ($imagefile['images'] as $img) {
+ if ($img->isValid() && ! $img->hasMoved()) {
+ $newName = $img->getRandomName();
+ $img->move(WRITEPATH . 'uploads', $newName);
+ }
+ }
+}
+```
+
+Here, `images` is a loop from the form field name.
+
+If there are multiple files with the same name, you can use `getFile()` to retrieve every file individually.
+
+In the controller:
+
+```php
+request->getFile('images.0');
+$file2 = $this->request->getFile('images.1');
+```
+
+You might find it easier to use `getFileMultiple()` to get an array of uploaded files with the same name:
+
+```php
+request->getFileMultiple('images');
+```
+
+Another example:
+
+```
+Upload an avatar:
+Upload an avatar:
+```
+
+In the controller:
+
+```php
+request->getFile('my-form.details.avatars.0');
+$file2 = $this->request->getFile('my-form.details.avatars.1');
+```
+
+!!! note "Note"
+ Using `getFiles()` is more appropriate.
+
+## Working with the File
+
+Once you've retrieved the UploadedFile instance, you can get information about the file in safe ways, as well as move the file to a new location.
+
+### Verify a File
+
+You can check that a file was actually uploaded via HTTP with no errors by calling the `isValid()` method:
+
+```php
+isValid()) {
+ throw new \RuntimeException($file->getErrorString() . '(' . $file->getError() . ')');
+}
+```
+
+As seen in this example, if a file had an upload error, you can retrieve the error code (an integer) and the error message with the `getError()` and `getErrorString()` methods. The following errors can be discovered through this method:
+
+- The file exceeds your `upload_max_filesize` ini directive.
+- The file exceeds the upload limit defined in your form.
+- The file was only partially uploaded.
+- No file was uploaded.
+- The file could not be written on disk.
+- File could not be uploaded: missing temporary directory.
+- File upload was stopped by a PHP extension.
+
+### File Names
+
+#### getName()
+
+You can retrieve the original filename provided by the client with the `getName()` method. This will typically be the filename sent by the client, and should not be trusted. If the file has been moved, this will return the final name of the moved file:
+
+```php
+getName();
+```
+
+#### getClientName()
+
+Always returns the original name of the uploaded file as sent by the client, even if the file has been moved:
+
+```php
+getClientName();
+```
+
+#### getTempName()
+
+To get the full path of the temp file that was created during the upload, you can use the `getTempName()` method:
+
+```php
+getTempName();
+```
+
+### Other File Info
+
+#### getClientExtension()
+
+Returns the original file extension, based on the file name that was uploaded:
+
+```php
+getClientExtension();
+```
+
+!!! warning "Warning"
+ This is NOT a trusted source. For a trusted version, use `guessExtension()` instead.
+
+#### getClientMimeType()
+
+Returns the mime type of the file as provided by the client. This is NOT a trusted value. For a trusted version, use `getMimeType()` instead:
+
+```php
+getClientMimeType();
+
+echo $type; // image/png
+```
+
+#### getClientPath()
+
+!!! success "Available from version 4.4.0"
+
+Returns the [webkit relative path](https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath) of the uploaded file when the client has uploaded files via directory upload. In PHP versions below 8.1, this returns `null`.
+
+```php
+getClientPath();
+echo $clientPath; // dir/file.txt, or dir/sub_dir/file.txt
+```
+
+### Moving Files
+
+!!! warning "Warning"
+ Do not let user input directly decide the destination directory. The `$targetPath` argument of `move()` and the `$folderName` argument of `store()` are never sanitized, so an attacker-controlled value containing `../` can move the uploaded file outside the intended directory. Use directories controlled by your application.
+
+ Filenames are only sanitized by default: `move()` sanitizes the client-provided name when the `$name` argument is omitted, and `store()` generates a random name when `$fileName` is omitted. A caller-supplied `$name` or `$fileName` is NOT sanitized, so if you pass one, generate it yourself or sanitize it with `sanitize_filename`.
+
+ Sanitizing a client-provided filename does not make it safe to keep in a web-accessible directory where scripts can execute. Some web servers select a handler from any extension in a filename, including an extension before the final one. Keep uploads outside the public directory with `store()` when possible. If they must be public, disable script execution in the upload directory and pass `$file->getRandomName()` as the second argument to `move()`.
+
+#### with Original Filename
+
+Each file can be moved to its new location with the aptly named `move()` method. This takes the directory to move the file to as the first parameter:
+
+```php
+move(WRITEPATH . 'uploads');
+```
+
+By default, the client-provided filename is sanitized and used.
+
+#### with New Filename
+
+You can specify a new filename by passing it as the second parameter:
+
+```php
+getRandomName();
+$file->move(WRITEPATH . 'uploads', $newName);
+```
+
+#### Overwriting Existing File
+
+By default, if the destination file already exists, a new filename will be used. For example, if **image_name.jpg** already exists in the directory, then the filename will be **image_name_1.jpg** automatically.
+
+You can overwrite the existing file by passing `true` as the third parameter:
+
+```php
+move(WRITEPATH . 'uploads', null, true);
+```
+
+#### Check if the File Moved
+
+Once the file has been moved, the temporary file is deleted. You can check if a file has been moved already with the `hasMoved()` method, which returns a boolean:
+
+```php
+isValid() && ! $file->hasMoved()) {
+ $file->move($path);
+}
+```
+
+#### When Moving Fails
+
+Moving an uploaded file can fail, with an `HTTPException`, under several circumstances:
+
+- the file has already been moved
+- the file did not upload successfully
+- the file move operation fails (e.g., improper permissions)
+
+### Store Files
+
+Each file can be moved to its new location with the aptly named `store()` method.
+
+With the simplest usage, a single file might be submitted like:
+
+```
+
+```
+
+By default, uploaded files are saved in the **writable/uploads** directory. A **YYYYMMDD** folder and a random file name will be created. The method returns a file path:
+
+```php
+request->getFile('userfile')->store();
+```
+
+You can specify a directory to move the file to as the first parameter, and a new filename by passing it as the second parameter:
+
+```php
+request->getFile('userfile')->store('head_img/', 'user_name.jpg');
+```
+
+Moving an uploaded file can fail, with an `HTTPException`, under several circumstances:
+
+- the file has already been moved
+- the file did not upload successfully
+- the file move operation fails (e.g., improper permissions)
diff --git a/docs/libraries/uri.md b/docs/libraries/uri.md
new file mode 100644
index 000000000000..bbbd258365d2
--- /dev/null
+++ b/docs/libraries/uri.md
@@ -0,0 +1,366 @@
+# Working with URIs
+
+CodeIgniter provides an object-oriented solution for working with URIs in your application. Using this makes it simple to ensure that the structure is always correct, no matter how complex the URI might be, as well as to add a relative URI to an existing one and have it resolved safely and correctly.
+
+## Creating URI instances
+
+Creating a URI instance is as simple as creating a new class instance.
+
+When you create the new instance, you can pass a full or partial URL in the constructor and it will be parsed into its appropriate sections:
+
+```php
+$uri = new \CodeIgniter\HTTP\URI('http://www.example.com/some/path');
+```
+
+Alternatively, you can use the `service()` function to return an instance for you:
+
+```php
+$uri = service('uri', 'http://www.example.com/some/path');
+```
+
+Since v4.4.0, if you don't pass a URL, it returns the current URI:
+
+```php
+$uri = service('uri'); // returns the current SiteURI instance.
+```
+
+!!! note "Note"
+ The above code returns a `SiteURI` instance, which extends the `URI` class. The `URI` class is for general URIs, but the `SiteURI` class is for your site URIs.
+
+### The Current URI
+
+When you need an object representing the current URL of the request, you can use the `current_url()` function available in the [URL Helper](../helpers/url_helper.md):
+
+```php
+$uri = current_url(true);
+```
+
+You must pass `true` as the first parameter. Otherwise, it will return the string representation of the current URL.
+
+This URI is based on the path (relative to your `baseURL`) as determined by the current request object and your settings in `Config\App` (`baseURL`, `indexPage`, and `forceGlobalSecureRequests`).
+
+Assuming that you're in a controller that extends `CodeIgniter\Controller`, you can also get the current SiteURI instance:
+
+```php
+$uri = $this->request->getUri();
+```
+
+## URI Strings
+
+Many times, all you really want is to get a string representation of a URI. This is easy to do by simply casting the URI as a string:
+
+```php
+getScheme(); // 'http'
+$uri->setScheme('https');
+```
+
+### Authority
+
+Many URIs contain several elements that are collectively known as the 'authority'. This includes any user info, the host and the port number. You can retrieve all of these pieces as one single string with the `getAuthority()` method, or you can manipulate the individual parts.
+
+```php
+getAuthority(); // user@example.com:21
+```
+
+By default, this will not display the password portion since you wouldn't want to show that to anyone. If you want to show the password, you can use the `showPassword()` method. This URI instance will continue to show that password until you turn it off again, so always make sure that you turn it off as soon as you are finished with it:
+
+```php
+getAuthority(); // user@example.com:21
+echo $uri->showPassword()->getAuthority(); // user:password@example.com:21
+
+// Turn password display off again.
+$uri->showPassword(false);
+```
+
+If you do not want to display the port, pass in `true` as the only parameter:
+
+```php
+getAuthority(true); // user@example.com
+```
+
+!!! note "Note"
+ If the current port is the default port for the scheme, it will never be displayed.
+
+### UserInfo
+
+The userinfo section is simply the username and password that you might see with an FTP URI. While you can get this as part of the Authority, you can also retrieve it yourself:
+
+```php
+getUserInfo(); // user
+```
+
+By default, it will not display the password, but you can override that with the `showPassword()` method:
+
+```php
+showPassword()->getUserInfo(); // user:password
+$uri->showPassword(false);
+```
+
+### Host
+
+The host portion of the URI is typically the domain name of the URL. This can be easily set and retrieved with the `getHost()` and `setHost()` methods:
+
+```php
+getHost(); // www.example.com
+echo $uri->setHost('anotherexample.com')->getHost(); // anotherexample.com
+```
+
+### Port
+
+The port is an integer between 0 and 65535. Each scheme has a default value associated with it.
+
+```php
+getPort(); // 21
+echo $uri->setPort(2201)->getPort(); // 2201
+```
+
+When using the `setPort()` method, the port will be checked to ensure that it is within the valid range, and then assigned.
+
+### Path
+
+The path is all of the segments within the site itself. As expected, the `getPath()` and `setPath()` methods can be used to manipulate it:
+
+```php
+getPath(); // '/some/path'
+echo $uri->setPath('/another/path')->getPath(); // '/another/path'
+```
+
+!!! note "Note"
+ When the path is set, it is sanitized to encode any dangerous characters and remove dot segments for safety.
+
+!!! note "Note"
+ Since v4.4.0, the `SiteURI::getRoutePath()` method returns the URI path relative to baseURL, and the `SiteURI::getPath()` method always returns the full URI path with leading `/`.
+
+### Query
+
+The query data can be manipulated through the class using simple string representations.
+
+#### Getting/Setting Query
+
+Query values can only be set as a string currently.
+
+```php
+getQuery(); // 'foo=bar'
+$uri->setQuery('foo=bar&bar=baz');
+```
+
+The `setQuery()` method overwrites existing query variables.
+
+!!! note "Note"
+ Query values cannot contain fragments. An InvalidArgumentException will be thrown if they do.
+
+#### Setting Query from Array
+
+You can set query values using an array:
+
+```php
+setQueryArray(['foo' => 'bar', 'bar' => 'baz']);
+```
+
+The `setQueryArray()` method overwrites existing query variables.
+
+#### Adding Query Value
+
+You can add a value to the query variables collection without destroying the existing query variables with the `addQuery()` method. The first parameter is the name of the variable, and the second parameter is the value:
+
+```php
+addQuery('foo', 'bar');
+```
+
+#### Filtering Query Values
+
+You can filter the query values returned by passing an options array to the `getQuery()` method, with either an *only* or an *except* key:
+
+```php
+getQuery(['only' => ['foo']]);
+
+// Returns 'foo=bar&baz=foz'
+echo $uri->getQuery(['except' => ['bar']]);
+```
+
+This only changes the values returned during this one call.
+
+#### Changing Query Values
+
+If you need to modify the URI's query values more permanently, you can use the `stripQuery()` and `keepQuery()` methods to change the actual object's query variable collection:
+
+```php
+stripQuery('foo', 'bar');
+
+// Leaves just the 'foo' variable
+$uri->keepQuery('foo');
+```
+
+!!! note "Note"
+ By default, the `setQuery()` and `setQueryArray()` methods use the native `parse_str()` function to prepare data. If you want to use more liberal rules (which allow key names to contain dots), you can use a special method `useRawQueryString()` beforehand.
+
+### Fragment
+
+Fragments are the portion at the end of the URL, preceded by the pound sign (`#`). In HTML URLs these are links to an on-page anchor. Media URIs can make use of them in various other ways.
+
+```php
+getFragment(); // 'first-heading'
+echo $uri->setFragment('second-heading')->getFragment(); // 'second-heading'
+```
+
+## URI Segments
+
+Each section of the path between the slashes is a single segment.
+
+!!! note "Note"
+ In the case of your site URI, URI Segments mean only the URI path part relative to the baseURL. If your baseURL contains subfolders, the values will be different from the current URI path.
+
+The URI class provides a simple way to determine what the values of the segments are. The segments start at 1 being the furthest left of the path.
+
+```php
+getSegment(1) === 'users') {
+ echo $uri->getSegment(2);
+}
+```
+
+You can also set a different default value for a particular segment by using the second parameter of the `getSegment()` method. The default is an empty string.
+
+```php
+getSegment(3, 'foo');
+// will print 'bar'
+echo $uri->getSegment(4, 'bar');
+// will throw an exception
+echo $uri->getSegment(5, 'baz');
+// will print 'baz'
+echo $uri->setSilent()->getSegment(5, 'baz');
+// will print '' (empty string)
+echo $uri->setSilent()->getSegment(5);
+```
+
+!!! note "Note"
+ You can get the last +1 segment. When you try to get the last +2 or higher segment, an exception will be thrown by default. You can prevent exceptions from being thrown with the `setSilent()` method.
+
+You can get a count of the total segments:
+
+```php
+getTotalSegments(); // 3
+```
+
+Finally, you can retrieve an array of all of the segments:
+
+```php
+getSegments();
+
+/*
+ * Produces:
+ * [
+ * 0 => 'users',
+ * 1 => '15',
+ * 2 => 'profile',
+ * ]
+ */
+```
+
+## Disable Throwing Exceptions
+
+By default, some methods of this class may throw an exception. If you want to disable it, you can set a special flag that will prevent throwing exceptions.
+
+```php
+setSilent();
+
+// Enable throwing exceptions (default)
+$uri->setSilent(false);
+```
diff --git a/docs/libraries/user_agent.md b/docs/libraries/user_agent.md
new file mode 100644
index 000000000000..0394edc6fd2b
--- /dev/null
+++ b/docs/libraries/user_agent.md
@@ -0,0 +1,187 @@
+# User Agent Class
+
+The User Agent Class provides functions that help identify information about the browser, mobile device, or robot visiting your site.
+
+## Using the User Agent Class
+
+### Initializing the Class
+
+The User Agent class is always available directly from the current [IncomingRequest](../incoming/incomingrequest.md) instance. By default, you will have a request instance in your controller that you can retrieve the User Agent class from:
+
+```php
+request->getUserAgent();
+```
+
+### User Agent Definitions
+
+The user agent name definitions are in a config file located at: **app/Config/UserAgents.php**. You may add items to the various user agent arrays if needed.
+
+### Example
+
+When the User Agent class is initialized, it will attempt to determine whether the user agent browsing your site is a web browser, a mobile device, or a robot. It will also gather the platform information if it is available:
+
+```php
+request->getUserAgent();
+
+if ($agent->isBrowser()) {
+ $currentAgent = $agent->getBrowser() . ' ' . $agent->getVersion();
+} elseif ($agent->isRobot()) {
+ $currentAgent = $agent->getRobot();
+} elseif ($agent->isMobile()) {
+ $currentAgent = $agent->getMobile();
+} else {
+ $currentAgent = 'Unidentified User Agent';
+}
+
+echo $currentAgent;
+
+echo $agent->getPlatform(); // Platform info (Windows, Linux, Mac, etc.)
+```
+
+## Class Reference
+
+## CodeIgniter\HTTP
+
+### UserAgent
+
+#### isBrowser([$key = null])
+
+- **Parameters**
+ - **$key** `string` Optional browser name
+
+- **Returns**: true if the user agent is a (specified) browser, false if not
+
+- **Return type**: `bool`
+
+Returns true/false (boolean) if the user agent is a known web browser.
+
+```php
+isBrowser('Safari')) {
+ echo 'You are using Safari.';
+} elseif ($agent->isBrowser()) {
+ echo 'You are using a browser.';
+}
+```
+
+!!! note "Note"
+ The string "Safari" in this example is an array key in the list of browser definitions. You can find this list in **app/Config/UserAgents.php** if you want to add new browsers or change the strings.
+
+#### isMobile([$key = null])
+
+- **Parameters**
+ - **$key** `string` Optional mobile device name
+
+- **Returns**: true if the user agent is a (specified) mobile device, false if not
+
+- **Return type**: `bool`
+
+Returns true/false (boolean) if the user agent is a known mobile device.
+
+```php
+isMobile('iphone')) {
+ echo view('iphone/home');
+} elseif ($agent->isMobile()) {
+ echo view('mobile/home');
+} else {
+ echo view('web/home');
+}
+```
+
+#### isRobot([$key = null])
+
+- **Parameters**
+ - **$key** `string` Optional robot name
+
+- **Returns**: true if the user agent is a (specified) robot, false if not
+
+- **Return type**: `bool`
+
+Returns true/false (boolean) if the user agent is a known robot.
+
+!!! note "Note"
+ The user agent library only contains the most common robot definitions. It is not a complete list of bots. There are hundreds of them, so searching for each one would not be very efficient. If you find that some bots that commonly visit your site are missing from the list, you can add them to your **app/Config/UserAgents.php** file.
+
+#### isReferral()
+
+- **Returns**: true if the user agent is a referral, false if not
+- **Return type**: `bool`
+
+Returns true/false (boolean) if the user agent was referred from another site.
+
+#### getBrowser()
+
+- **Returns**: Detected browser or an empty string
+- **Return type**: `string`
+
+Returns a string containing the name of the web browser viewing your site.
+
+#### getVersion()
+
+- **Returns**: Detected browser version or an empty string
+- **Return type**: `string`
+
+Returns a string containing the version number of the web browser viewing your site.
+
+#### getMobile()
+
+- **Returns**: Detected mobile device brand or an empty string
+- **Return type**: `string`
+
+Returns a string containing the name of the mobile device viewing your site.
+
+#### getRobot()
+
+- **Returns**: Detected robot name or an empty string
+- **Return type**: `string`
+
+Returns a string containing the name of the robot viewing your site.
+
+#### getPlatform()
+
+- **Returns**: Detected operating system or an empty string
+- **Return type**: `string`
+
+Returns a string containing the platform viewing your site (Linux, Windows, OS X, etc.).
+
+#### getReferrer()
+
+- **Returns**: Detected referrer or an empty string
+- **Return type**: `string`
+
+The referrer, if the user agent was referred from another site. Typically you'll test for this as follows:
+
+```php
+isReferral()) {
+ echo $agent->getReferrer();
+}
+```
+
+#### getAgentString()
+
+- **Returns**: Full user agent string or an empty string
+- **Return type**: `string`
+
+Returns a string containing the full user agent string. Typically it will be something like this:
+
+```
+Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.0.4) Gecko/20060613 Camino/1.0.2
+```
+
+#### parse($string)
+
+- **Parameters**
+ - **$string** `string` A custom user-agent string
+
+- **Return type**: `void`
+
+Parses a custom user-agent string, different from the one reported by the current visitor.
diff --git a/docs/libraries/validation.md b/docs/libraries/validation.md
new file mode 100644
index 000000000000..c8d686143501
--- /dev/null
+++ b/docs/libraries/validation.md
@@ -0,0 +1,1335 @@
+# Validation
+
+CodeIgniter provides a comprehensive data validation class that helps minimize the amount of code you'll write.
+
+## Overview
+
+Before explaining CodeIgniter's approach to data validation, let's describe the ideal scenario:
+
+1. A form is displayed.
+2. You fill it in and submit it.
+3. If you submitted something invalid, or perhaps missed a required item, the form is redisplayed containing your data along with an error message describing the problem.
+4. This process continues until you have submitted a valid form.
+
+On the receiving end, the script must:
+
+1. Check for required data.
+2. Verify that the data is of the correct type, and meets the correct criteria. For example, if a username is submitted it must be validated to contain only permitted characters. It must be of a minimum length, and not exceed a maximum length. The username can't be someone else's existing username, or perhaps even a reserved word. Etc.
+3. Sanitize the data for security.
+4. Pre-format the data if needed.
+5. Prep the data for insertion in the database.
+
+Although there is nothing terribly complex about the above process, it usually requires a significant amount of code, and to display error messages, various control structures are usually placed within the form HTML. Form validation, while simple to create, is generally very messy and tedious to implement.
+
+## Form Validation Tutorial
+
+What follows is a "hands-on" tutorial for implementing CodeIgniter's Form Validation.
+
+In order to implement form validation, you'll need three things:
+
+1. A [View](../outgoing/views.md) file containing a form.
+2. A View file containing a "success" message to be displayed upon successful submission.
+3. A [controller](../incoming/controllers.md) method to receive and process the submitted data.
+
+Let's create those three things, using a member sign-up form as the example.
+
+### The Form
+
+Using a text editor, create a form called **signup.php**. In it, place this code and save it to your **app/Views/** folder:
+
+```
+
+
+ My Form
+
+
+
+ = validation_list_errors() ?>
+
+ = form_open('form') ?>
+
+ Username
+
+
+ Password
+
+
+ Password Confirm
+
+
+ Email Address
+
+
+
+
+ = form_close() ?>
+
+
+
+```
+
+### The Success Page
+
+Using a text editor, create a form called **success.php**. In it, place this code and save it to your **app/Views/** folder:
+
+```
+
+
+ My Form
+
+
+
+ Your form was successfully submitted!
+
+ = anchor('form', 'Try it again!') ?>
+
+
+
+```
+
+### The Controller
+
+Using a text editor, create a controller called **Form.php**. In it, place this code and save it to your **app/Controllers/** folder:
+
+```php
+request->is('post')) {
+ return view('signup');
+ }
+
+ $rules = [
+ // @TODO
+ ];
+
+ $data = $this->request->getPost(array_keys($rules));
+
+ if (! $this->validateData($data, $rules)) {
+ return view('signup');
+ }
+
+ // If you want to get the validated data.
+ $validData = $this->validator->getValidated();
+
+ return view('success');
+ }
+}
+```
+
+!!! note "Note"
+ The [$this->request->is()](../incoming/incomingrequest.md#is) method can be used since v4.3.0. In previous versions, you need to use `if (strtolower($this->request->getMethod()) !== 'post')`.
+
+!!! note "Note"
+ The [$this->validator->getValidated()](#getting-validated-data) method can be used since v4.4.0.
+
+### The Routes
+
+Then add routes for the controller in **app/Config/Routes.php**:
+
+```php
+// ...
+
+$routes->get('form', 'Form::index');
+$routes->post('form', 'Form::index');
+
+// ...
+```
+
+### Try it!
+
+To try your form, visit your site using a URL similar to this one:
+
+```
+example.com/index.php/form/
+```
+
+If you submit the form, you should simply see the form reload. That's because you haven't set up any validation rules in [$this->validateData()](../incoming/controllers.md#this-validatedata) yet.
+
+The `validateData()` method is a method in the Controller. It uses the **Validation class** inside. See [$this->validateData()](../incoming/controllers.md#this-validatedata).
+
+!!! note "Note"
+ Since you haven't told the `validateData()` method to validate anything yet, it **returns false** (boolean false) **by default**. The `validateData()` method only returns true if it has successfully applied your rules without any of them failing.
+
+### Explanation
+
+You'll notice several things about the above pages.
+
+#### signup.php
+
+The form (**signup.php**) is a standard web form with a couple of exceptions:
+
+1. It uses a [form helper](../helpers/form_helper.md) to create the form opening and closing. Technically, this isn't necessary. You could create the form using standard HTML. However, the benefit of using the helper is that it generates the action URL for you, based on the URL in your config file. This makes your application more portable in the event your URLs change.
+
+2. At the top of the form, you'll notice the following function call:
+
+ ```
+ = validation_list_errors() ?>
+
+ This function will return any error messages sent back by the validator. If there are no messages, it returns an
+ empty string.
+ ```
+
+#### Form.php
+
+The controller (**Form.php**) has one property: `$helpers`. It loads the form helper used by your view files.
+
+The controller has one method: `index()`. This method returns the **signup** view to show the form when a non-POST request comes. Otherwise, it uses the Controller-provided [$this->validateData()](../incoming/controllers.md#this-validatedata) method. It also runs the validation routine. Based on whether the validation was successful, it either presents the form or the success page.
+
+### Add Validation Rules
+
+Then add validation rules in the controller (**Form.php**):
+
+```php
+// ...
+
+$rules = [
+ 'username' => 'required|max_length[30]',
+ 'password' => 'required|max_length[255]|min_length[10]',
+ 'passconf' => 'required|max_length[255]|matches[password]',
+ 'email' => 'required|max_length[254]|valid_email',
+];
+
+// ...
+```
+
+If you submit the form, you should see the success page or the form with error messages.
+
+## Config for Validation
+
+### Traditional and Strict Rules
+
+CodeIgniter 4 has two kinds of Validation rule classes.
+
+The default rule classes (**Strict Rules**) have the namespace `CodeIgniter\Validation\StrictRules`, and they provide strict validation.
+
+The traditional rule classes (**Traditional Rules**) have the namespace `CodeIgniter\Validation`. They are provided for backward compatibility only. They may not validate non-string values correctly and need not be used in new projects.
+
+!!! note "Note"
+ Since v4.3.0, **Strict Rules** are used by default for better security.
+
+#### Strict Rules
+
+!!! success "Available from version 4.2.0"
+
+The **Strict Rules** don't use implicit type conversion.
+
+#### Traditional Rules
+
+!!! important "Important"
+ Traditional Rules exist only for backward compatibility. Do not use them in new projects. Even if you are already using them, we recommend switching to Strict Rules.
+
+!!! warning "Warning"
+ When validating data that contains non-string values, such as JSON data, you should use **Strict Rules**.
+
+The **Traditional Rules** implicitly assume that string values are validated, and the input value may be converted implicitly to a string value. It works for most basic cases like validating POST data.
+
+However, for example, if you use JSON input data, it may be of type bool/null/array. When you validate the boolean `true`, it is converted to the string `'1'` with the Traditional rule classes. If you validate it with the `integer` rule, `'1'` passes the validation.
+
+#### Using Traditional Rules
+
+!!! warning "Warning"
+ The **Traditional Rules** are provided for backward compatibility only. They may not validate non-string values correctly and need not be used in new projects.
+
+If you want to use traditional rules, you need to change the rule classes in **app/Config/Validation.php**:
+
+```php
+setRule('username', 'Username', 'required|max_length[30]|min_length[3]');
+$validation->setRule('password', 'Password', ['required', 'max_length[255]', 'min_length[8]', 'alpha_numeric_punct']);
+```
+
+The value you pass to `$field` must match the key of any data array that is sent in. If the data is taken directly from `$_POST`, then it must be an exact match for the form input name.
+
+!!! warning "Warning"
+ Prior to v4.2.0, this method's third parameter, `$rules`, was typehinted to accept `string`. In v4.2.0 and after, the typehint was removed to allow arrays, too. To avoid LSP being broken in extending classes overriding this method, the child class's method should also be modified to remove the typehint.
+
+### Setting Multiple Rules
+
+#### setRules()
+
+Like `setRule()`, but accepts an array of field names and their rules:
+
+```php
+$validation->setRules([
+ 'username' => 'required|max_length[30]',
+ 'password' => 'required|max_length[255]|min_length[10]',
+]);
+// or
+$validation->setRules([
+ 'username' => ['required', 'max_length[30]'],
+ 'password' => ['required', 'max_length[255]', 'min_length[10]'],
+]);
+```
+
+To give a labeled error message, you can set it up as follows:
+
+```php
+$validation->setRules([
+ 'username' => ['label' => 'Username', 'rules' => 'required|max_length[30]'],
+ 'password' => ['label' => 'Password', 'rules' => 'required|max_length[255]|min_length[10]'],
+]);
+// or
+$validation->setRules([
+ 'username' => ['label' => 'Username', 'rules' => 'required|max_length[30]'],
+ 'password' => ['label' => 'Password', 'rules' => ['required', 'max_length[255]', 'min_length[10]']],
+]);
+```
+
+!!! note "Note"
+ `setRules()` will overwrite any rules that were set previously. To add more than one rule to an existing set of rules, use `setRule()` multiple times.
+
+### Setting Rules for Array Data
+
+If your data is in a nested associative array, you can use "dot array syntax" to easily validate your data:
+
+```php
+/*
+ * The data to test:
+ * [
+ * 'contacts' => [
+ * 'name' => 'Joe Smith',
+ * 'friends' => [
+ * [
+ * 'name' => 'Fred Flinstone',
+ * ],
+ * [
+ * 'name' => 'Wilma',
+ * ],
+ * ]
+ * ]
+ * ]
+ */
+
+// Joe Smith
+$validation->setRules([
+ 'contacts.name' => 'required|max_length[60]',
+]);
+```
+
+You can use the `*` wildcard symbol to match any one level of the array:
+
+```php
+// Fred Flintsone & Wilma
+$validation->setRules([
+ 'contacts.friends.*.name' => 'required|max_length[60]',
+]);
+```
+
+!!! note "Note"
+ Prior to v4.4.4, due to a bug, the wildcard `*` validated data in incorrect dimensions. See [Upgrading](../installation/upgrade_444.md#validation-with-dot-array-syntax) for details.
+
+The "dot array syntax" can also be useful when you have single-dimension array data. For example, data returned by a multi-select dropdown:
+
+```php
+/*
+ * The data to test:
+ * [
+ * 'user_ids' => [
+ * 1,
+ * 2,
+ * 3,
+ * ]
+ * ]
+ */
+
+// Rule
+$validation->setRules([
+ 'user_ids.*' => 'required|max_length[19]',
+]);
+```
+
+### withRequest()
+
+!!! important "Important"
+ This method exists only for backward compatibility. Do not use it in new projects. Even if you are already using it, we recommend that you use another, more appropriate method.
+
+!!! warning "Warning"
+ If you want to validate POST data only, don't use `withRequest()`. This method uses [$request->getVar()](../incoming/incomingrequest.md#getting-data) which returns `$_GET`, `$_POST` or `$_COOKIE` data in that order (depending on **php.ini** [request-order](https://www.php.net/manual/en/ini.core.php#ini.request-order)). Newer values override older values. POST values may be overridden by the cookies if they have the same name.
+
+One of the most common times you will use the validation library is when validating data that was input from an HTTP Request. If desired, you can pass an instance of the current Request object and it will take all of the input data and set it as the data to be validated:
+
+```php
+$validation = service('validation');
+$request = service('request');
+
+if ($validation->withRequest($request)->run()) {
+ // If you use the input data, you should get it from the getValidated() method.
+ // Otherwise you may create a vulnerability.
+ $validData = $validation->getValidated();
+
+ // ...
+}
+```
+
+!!! warning "Warning"
+ When you use this method, you should use the [getValidated()](#getting-validated-data) method to get the validated data. Because this method gets JSON data from [$request->getJSON()](../incoming/incomingrequest.md#getting-json-data) when the request is a JSON request (`Content-Type: application/json`), or gets raw data from [$request->getRawInput()](../incoming/incomingrequest.md#retrieving-raw-data-put-patch-delete) when the request is a PUT, PATCH, or DELETE request and is not an HTML form post (`Content-Type: multipart/form-data`), or gets data from [$request->getVar()](../incoming/incomingrequest.md#getting-data), and an attacker could change what data is validated.
+
+!!! note "Note"
+ The [getValidated()](#getting-validated-data) method can be used since v4.4.0.
+
+## Working with Validation
+
+### Running Validation
+
+The `run()` method runs validation. It has the method signature:
+
+```
+run(?array $data = null, ?string $group = null, ?string $dbGroup = null): bool
+```
+
+The `$data` is an array of data to validate. The optional second parameter `$group` is the [predefined group of rules](#how-to-save-your-rules) to apply. The optional third parameter `$dbGroup` is the database group to use.
+
+This method returns true if the validation is successful.
+
+```php
+if (! $validation->run($data)) {
+ // handle validation errors
+}
+// or
+if (! $validation->run($data, 'signup')) {
+ // handle validation errors
+}
+```
+
+### Running Multiple Validations
+
+!!! note "Note"
+ The `run()` method will not reset the error state. Should a previous run fail, `run()` will always return false and `getErrors()` will return all previous errors until explicitly reset.
+
+If you intend to run multiple validations, for instance on different data sets or with different rules after one another, you might need to call `$validation->reset()` before each run to get rid of errors from the previous run. Be aware that `reset()` will invalidate any data, rule or custom error you previously set, so `setRules()`, `setRuleGroup()` etc. need to be repeated:
+
+```php
+foreach ($userAccounts as $user) {
+ $validation->reset();
+ $validation->setRules($userAccountRules);
+
+ if (! $validation->run($user)) {
+ // handle validation errors
+ }
+}
+```
+
+### Validating 1 Value
+
+The `check()` method validates one value against the rules. The first parameter `$value` is the value to validate. The second parameter `$rule` holds the validation rules. The optional third parameter `$errors` is the custom error message.
+
+```php
+if ($validation->check($value, 'required')) {
+ // $value is valid.
+}
+```
+
+!!! note "Note"
+ Prior to v4.4.0, this method's second parameter, `$rule`, was typehinted to accept `string`. In v4.4.0 and after, the typehint was removed to allow arrays, too.
+
+!!! note "Note"
+ This method calls the `setRule()` method to set the rules internally.
+
+### Getting Validated Data
+
+!!! success "Available from version 4.4.0"
+
+The actual validated data can be retrieved with the `getValidated()` method. This method returns an array of only those elements that have been validated by the validation rules.
+
+```php
+$validation = service('validation');
+$validation->setRules([
+ 'username' => 'required',
+ 'password' => 'required|min_length[10]',
+]);
+
+$data = [
+ 'username' => 'john',
+ 'password' => 'BPi-$Swu7U5lm$dX',
+ 'csrf_token' => '8b9218a55906f9dcc1dc263dce7f005a',
+];
+
+if ($validation->run($data)) {
+ $validatedData = $validation->getValidated();
+ // $validatedData = [
+ // 'username' => 'john',
+ // 'password' => 'BPi-$Swu7U5lm$dX',
+ // ];
+}
+```
+
+```php
+// In Controller.
+
+if (! $this->validateData($data, [
+ 'username' => 'required',
+ 'password' => 'required|min_length[10]',
+])) {
+ // The validation failed.
+ return view('login', [
+ 'errors' => $this->validator->getErrors(),
+ ]);
+}
+
+// The validation was successful.
+
+// Get the validated data.
+$validData = $this->validator->getValidated();
+```
+
+### Saving Sets of Validation Rules to the Config File
+
+A nice feature of the Validation class is that it permits you to store all your validation rules for your entire application in a config file. You organize the rules into "groups". You can specify a different group every time you run the validation.
+
+#### How to Save Your Rules
+
+To store your validation rules, simply create a new public property in the `Config\Validation` class with the name of your group. This element will hold an array with your validation rules. As shown earlier, the validation array will have this prototype:
+
+```php
+ 'required|max_length[30]',
+ 'password' => 'required|max_length[255]',
+ 'pass_confirm' => 'required|max_length[255]|matches[password]',
+ 'email' => 'required|max_length[254]|valid_email',
+ ];
+
+ // ...
+}
+```
+
+#### How to Specify Rule Group
+
+You can specify the group to use when you call the `run()` method:
+
+```php
+$validation->run($data, 'signup');
+```
+
+#### How to Save Error Messages
+
+You can also store custom error messages in this configuration file by naming the property the same as the group, with `_errors` appended. These will automatically be used for any errors when this group is used:
+
+```php
+ 'required|max_length[30]',
+ 'password' => 'required|max_length[255]',
+ 'pass_confirm' => 'required|max_length[255]|matches[password]',
+ 'email' => 'required|max_length[254]|valid_email',
+ ];
+
+ public array $signup_errors = [
+ 'username' => [
+ 'required' => 'You must choose a username.',
+ ],
+ 'email' => [
+ 'valid_email' => 'Please check the Email field. It does not appear to be valid.',
+ ],
+ ];
+
+ // ...
+}
+```
+
+Or pass all settings in an array:
+
+```php
+ [
+ 'rules' => 'required|max_length[30]',
+ 'errors' => [
+ 'required' => 'You must choose a Username.',
+ ],
+ ],
+ 'email' => [
+ 'rules' => 'required|max_length[254]|valid_email',
+ 'errors' => [
+ 'valid_email' => 'Please check the Email field. It does not appear to be valid.',
+ ],
+ ],
+ ];
+
+ // ...
+}
+```
+
+See [Setting Custom Error Messages](#setting-custom-error-messages) for details on the formatting of the array.
+
+#### Getting & Setting Rule Groups
+
+##### Get Rule Group
+
+This method gets a rule group from the validation configuration:
+
+```php
+$validation->getRuleGroup('signup');
+```
+
+##### Set Rule Group
+
+This method sets a rule group from the validation configuration to the validation service:
+
+```php
+$validation->setRuleGroup('signup');
+```
+
+### Validation Placeholders
+
+The Validation class provides a simple method to replace parts of your rules based on data that's being passed into it. This sounds fairly obscure but can be especially handy with the `is_unique` validation rule.
+
+Placeholders are simply the name of the field (or array key) that was passed in as `$data` surrounded by curly brackets. This will be replaced by the **value** of the matched incoming field. An example should clarify this:
+
+```php
+$validation->setRules([
+ 'id' => 'max_length[19]|is_natural_no_zero',
+ 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]',
+]);
+```
+
+!!! warning "Warning"
+ Since v4.3.5, you must set the validation rules for the placeholder field (the `id` field in the sample code above) for security reasons, because attackers can send any data to your application.
+
+This set of rules states that the email address should be unique in the database, except for the row that has an id matching the placeholder's value. Assuming that the form POST data had the following:
+
+```php
+$_POST = [
+ 'id' => 4,
+ 'email' => 'foo@example.com',
+];
+```
+
+then the `{id}` placeholder would be replaced with the number **4**, giving this revised rule:
+
+```php
+$validation->setRules([
+ 'id' => 'max_length[19]|is_natural_no_zero',
+ 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,4]',
+]);
+```
+
+So it will ignore the row in the database that has `id=4` when it verifies the email is unique.
+
+!!! note "Note"
+ Since v4.3.5, if the placeholder (`id`) value does not pass the validation, the placeholder would not be replaced.
+
+This can also be used to create more dynamic rules at runtime, as long as you take care that any dynamic keys passed in don't conflict with your form data.
+
+## Working with Errors
+
+The Validation library provides several methods to help you set error messages, provide custom error messages, and retrieve one or more errors to display.
+
+By default, error messages are derived from language strings in **system/Language/en/Validation.php**, where each rule has an entry. In case you want to change a message default, create a file **app/Language/en/Validation.php** (and/or the corresponding folder of the locale you use in place of/besides `en`) and place in it keys and values of those error messages for which you want different defaults.
+
+### Setting Custom Error Messages
+
+Both the `setRule()` and `setRules()` methods can accept an array of custom messages that will be used as errors specific to each field as their last parameter. This allows for a very pleasant experience for the user since the errors are tailored to each instance. If no custom error message is provided, the default value will be used.
+
+There are two ways to provide custom error messages.
+
+As the last parameter:
+
+```php
+$validation->setRules(
+ [
+ 'username' => 'required|max_length[30]|is_unique[users.username]',
+ 'password' => 'required|max_length[254]|min_length[10]',
+ ],
+ [ // Errors
+ 'username' => [
+ 'required' => 'All accounts must have usernames provided',
+ ],
+ 'password' => [
+ 'min_length' => 'Your password is too short. You want to get hacked?',
+ ],
+ ],
+);
+```
+
+Or in a labeled style:
+
+```php
+$validation->setRules([
+ 'username' => [
+ 'label' => 'Username',
+ 'rules' => 'required|max_length[30]|is_unique[users.username]',
+ 'errors' => [
+ 'required' => 'All accounts must have {field} provided',
+ ],
+ ],
+ 'password' => [
+ 'label' => 'Password',
+ 'rules' => 'required|max_length[255]|min_length[10]',
+ 'errors' => [
+ 'min_length' => 'Your {field} is too short. You want to get hacked?',
+ ],
+ ],
+]);
+```
+
+If you'd like to include a field's "human" name, or the optional parameter some rules allow for (such as max_length), or the value that was validated, you can add the `{field}`, `{param}` and `{value}` tags to your message, respectively:
+
+```
+'min_length' => 'Supplied value ({value}) for {field} must have at least {param} characters.'
+```
+
+On a field with the human name Username and a rule of `min_length[6]` with a value of "Pizza", an error would display: "Supplied value (Pizza) for Username must have at least 6 characters."
+
+!!! warning "Warning"
+ If you get the error messages with `getErrors()` or `getError()`, the messages are not HTML-escaped. If you use user input data like `({value})` to make the error message, it might contain HTML tags. If you don't escape the messages before displaying them, XSS attacks are possible.
+
+!!! note "Note"
+ When using label-style error messages, if you pass the second parameter to `setRules()`, it will be overwritten with the value of the first parameter.
+
+### Translation of Messages and Validation Labels
+
+To use translated strings from language files, we can simply use the dot syntax. Let's say we have a file with translations located here: **app/Language/en/Rules.php**. We can simply use the language lines defined in this file, like this:
+
+```php
+$validation->setRules([
+ 'username' => [
+ 'label' => 'Rules.username',
+ 'rules' => 'required|max_length[30]|is_unique[users.username]',
+ 'errors' => [
+ 'required' => 'Rules.username.required',
+ ],
+ ],
+ 'password' => [
+ 'label' => 'Rules.password',
+ 'rules' => 'required|max_length[255]|min_length[10]',
+ 'errors' => [
+ 'min_length' => 'Rules.password.min_length',
+ ],
+ ],
+]);
+```
+
+### Getting All Errors
+
+If you need to retrieve all error messages for failed fields, you can use the `getErrors()` method:
+
+```php
+$errors = $validation->getErrors();
+/*
+ * Produces:
+ * [
+ * 'field1' => 'error message',
+ * 'field2' => 'error message',
+ * ]
+ */
+```
+
+If no errors exist, an empty array will be returned.
+
+When using a wildcard (`*`), the error will point to a specific field, replacing the asterisk with the appropriate key/keys:
+
+```
+// for data
+'contacts' => [
+ 'friends' => [
+ [
+ 'name' => 'Fred Flintstone',
+ ],
+ [
+ 'name' => '',
+ ],
+ ]
+]
+
+// rule
+'contacts.friends.*.name' => 'required'
+
+// error will be
+'contacts.friends.1.name' => 'The contacts.friends.*.name field is required.'
+```
+
+### Getting a Single Error
+
+You can retrieve the error for a single field with the `getError()` method. The only parameter is the field name:
+
+```php
+$error = $validation->getError('username');
+```
+
+If no error exists, an empty string will be returned.
+
+!!! note "Note"
+ When using a wildcard, all found errors that match the mask will be combined into one line separated by the EOL character.
+
+### Check If Error Exists
+
+You can check to see if an error exists with the `hasError()` method. The only parameter is the field name:
+
+```php
+if ($validation->hasError('username')) {
+ echo $validation->getError('username');
+}
+```
+
+When specifying a field with a wildcard, all errors matching the mask will be checked:
+
+```php
+/*
+ * For errors:
+ * [
+ * 'foo.0.bar' => 'Error',
+ * 'foo.baz.bar' => 'Error',
+ * ]
+ */
+
+// returns true
+$validation->hasError('foo.*.bar');
+```
+
+### Redirect and Validation Errors
+
+PHP shares nothing between requests. So when you redirect if a validation fails, there will be no validation errors in the redirected request because the validation has run in the previous request.
+
+In that case, you need to use the Form helper functions `validation_errors()`, `validation_list_errors()` and `validation_show_error()`. These functions check the validation errors that are stored in the session.
+
+To store the validation errors in the session, you need to use `withInput()` with `redirect()`:
+
+```php
+// In Controller.
+if (! $this->validateData($data, $rules)) {
+ return redirect()->back()->withInput();
+}
+```
+
+## Customizing Error Display
+
+When you call `$validation->listErrors()` or `$validation->showError()`, it loads a view file in the background that determines how the errors are displayed. By default, they display with a class of `errors` on the wrapping div. You can easily create new views and use them throughout your application.
+
+### Creating the Views
+
+The first step is to create custom views. These can be placed anywhere that the `view()` method can locate them, which means the standard View directory or any namespaced View folder will work. For example, you could create a new view at **app/Views/\_errors_list.php**:
+
+```php
+
+
+
+```
+
+An array named `$errors` is available within the view that contains a list of the errors, where the key is the name of the field that had the error, and the value is the error message, like this:
+
+```php
+$errors = [
+ 'username' => 'The username field must be unique.',
+ 'email' => 'You must provide a valid email address.',
+];
+```
+
+There are actually two types of views that you can create. The first has an array of all of the errors, and is what we just looked at. The other type is simpler, and only contains a single variable, `$error`, that contains the error message. This is used with the `showError()` method where a field must be specified:
+
+```
+= esc($error) ?>
+```
+
+### Configuration
+
+Once you have your views created, you need to let the Validation library know about them. Open **app/Config/Validation.php**. Inside, you'll find the `$templates` property where you can list as many custom views as you want, and provide a short alias they can be referenced by. If we were to add our example file from above, it would look something like:
+
+```php
+ 'CodeIgniter\Validation\Views\list',
+ 'single' => 'CodeIgniter\Validation\Views\single',
+ 'my_list' => '_errors_list',
+ ];
+
+ // ...
+}
+```
+
+### Specifying the Template
+
+You can specify the template to use by passing its alias as the first parameter in `listErrors()`:
+
+```
+= $validation->listErrors('my_list') ?>
+```
+
+When showing field-specific errors, you can pass the alias as the second parameter to the `showError()` method, right after the name of the field the error should belong to:
+
+```
+= $validation->showError('username', 'my_single') ?>
+```
+
+## Creating Custom Rules
+
+### Using Rule Classes
+
+Rules are stored within simple, namespaced classes. They can be stored in any location you would like, as long as the autoloader can find them. These files are called RuleSets.
+
+#### Adding a RuleSet
+
+To add a new RuleSet, edit **app/Config/Validation.php** and add the new file to the `$ruleSets` array:
+
+```php
+setRules([
+ 'foo' => 'required|max_length[19]|even',
+]);
+```
+
+#### Allowing Parameters
+
+If your method needs to work with parameters, the function will need a minimum of three parameters:
+
+1. the value to validate (`$value`)
+2. the parameter string (`$params`)
+3. an array with all of the data that was submitted with the form (`$data`)
+4. (optional) a custom error string (`&$error`), just as described above.
+
+!!! warning "Warning"
+ The field values in `$data` are unvalidated (or may be invalid). Using unvalidated input data is a source of vulnerability. You must perform the necessary validation within your custom rules before using the data in `$data`.
+
+The `$data` array is especially handy for rules like `required_with` that need to check the value of another submitted field to base its result on:
+
+```php
+required($value ?? '');
+
+ if ($present) {
+ return true;
+ }
+
+ // Still here? Then we fail this test if
+ // any of the fields are present in $data
+ // as $fields in the list
+ $requiredFields = [];
+
+ foreach ($params as $field) {
+ if (array_key_exists($field, $data)) {
+ $requiredFields[] = $field;
+ }
+ }
+
+ // Remove any keys with empty values since, that means they
+ // weren't truly there, as far as this is concerned.
+ $requiredFields = array_filter($requiredFields, static fn ($item) => ! empty($data[$item]));
+
+ return empty($requiredFields);
+ }
+}
+```
+
+### Using Closure Rule
+
+!!! success "Available from version 4.3.0"
+
+If you only need the functionality of a custom rule once throughout your application, you may use a closure instead of a rule class.
+
+You need to use an array for validation rules:
+
+```php
+$validation->setRules(
+ [
+ 'foo' => [
+ 'required',
+ static fn ($value) => (int) $value % 2 === 0,
+ ],
+ ],
+ [
+ // Errors
+ 'foo' => [
+ // Specify the array key for the closure rule.
+ 1 => 'The value is not even.',
+ ],
+ ],
+);
+
+if (! $validation->run($data)) {
+ // handle validation errors
+}
+```
+
+You must set the error message for the closure rule. When you specify the error message, set the array key for the closure rule. In the above code, the `required` rule has the key `0`, and the closure has `1`.
+
+Or you can use the following parameters:
+
+```php
+$validation->setRules([
+ 'foo' => [
+ 'required',
+ static function ($value, $data, &$error, $field) {
+ if ((int) $value % 2 === 0) {
+ return true;
+ }
+
+ $error = 'The value is not even.';
+
+ return false;
+ },
+ ],
+]);
+```
+
+### Using Callable Rule
+
+!!! success "Available from version 4.5.0"
+
+If you would like to use an array callback as a rule, you may use it instead of a Closure Rule.
+
+You need to use an array for validation rules:
+
+```php
+namespace App\Controllers;
+
+class Form extends BaseController
+{
+ // Define a custom validation rule.
+ public function _ruleEven($value): bool
+ {
+ return (int) $value % 2 === 0;
+ }
+
+ public function process()
+ {
+ // ...
+
+ $validation = service('validation');
+ $validation->setRules(
+ [
+ 'foo' => [
+ 'required',
+ // Specify the method in this controller as a rule.
+ [$this, '_ruleEven'],
+ ],
+ ],
+ [
+ // Errors
+ 'foo' => [
+ // Specify the array key for the callable rule.
+ 1 => 'The value is not even.',
+ ],
+ ],
+ );
+
+ if (! $validation->run($data)) {
+ // handle validation errors
+ }
+
+ // ...
+ }
+}
+```
+
+You must set the error message for the callable rule. When you specify the error message, set the array key for the callable rule. In the above code, the `required` rule has the key `0`, and the callable has `1`.
+
+Or you can use the following parameters:
+
+```php
+namespace App\Controllers;
+
+use Config\Services;
+
+class Form extends BaseController
+{
+ // Define a custom validation rule.
+ public function _ruleEven($value, $data, &$error, $field): bool
+ {
+ if ((int) $value % 2 === 0) {
+ return true;
+ }
+
+ $error = 'The value is not even.';
+
+ return false;
+ }
+
+ // ...
+}
+```
+
+## Available Rules
+
+!!! note "Note"
+ A rule is a string, so there must be **no spaces** between the parameters, especially for the `is_unique` rule. There can be no spaces before and after `ignore_value`.
+
+```php
+// is_unique[table.field,ignore_field,ignore_value]
+
+$validation->setRules([
+ 'name' => "max_length[36]|is_unique[supplier.name,uuid, {$uuid}]", // is not ok
+ 'name' => "max_length[36]|is_unique[supplier.name,uuid,{$uuid} ]", // is not ok
+ 'name' => "max_length[36]|is_unique[supplier.name,uuid,{$uuid}]", // is ok
+ 'name' => 'max_length[36]|is_unique[supplier.name,uuid,{uuid}]', // is ok - see "Validation Placeholders"
+]);
+// Warning: If `$uuid` is a user input, be sure to validate the format of the value before using it.
+// Otherwise, it is vulnerable.
+```
+
+### Rules for General Use
+
+The following is a list of all the native rules that are available to use:
+
+| Rule | Parameter | Description | Example |
+|----|----|----|----|
+| alpha | No | Fails if field has anything other than alphabetic characters in ASCII or is empty. | |
+| alpha_dash | No | Fails if field contains anything other than alphanumeric characters, underscores or dashes in ASCII or is empty. | |
+| alpha_numeric | No | Fails if field contains anything other than alphanumeric characters in ASCII or is empty. | |
+| alpha_numeric_punct | No | Fails if field contains anything other than alphanumeric, space, or this limited set of punctuation characters: `~` (tilde), `!` (exclamation), `#` (number), `$` (dollar), `%` (percent), `&` (ampersand), `*` (asterisk), `-` (dash), `_` (underscore), `+` (plus), `=` (equals), `|` (vertical bar), `:` (colon), `.` (period) or is empty. | |
+| alpha_numeric_space | No | Fails if field contains anything other than alphanumeric or space characters in ASCII or is empty. | |
+| alpha_space | No | Fails if field contains anything other than alphabetic characters or spaces in ASCII or is empty. | |
+| decimal | No | Fails if field contains anything other than a decimal number. Also accepts a `+` or `-` sign for the number. | |
+| differs | Yes | Fails if field does not differ from the one in the parameter. | `differs[field_name]` |
+| exact_length | Yes | Fails if field length is not exactly the parameter value. One or more comma-separated values are possible. | `exact_length[5]` or `exact_length[5,8,12]` |
+| field_exists | Yes | Fails if field does not exist. (This rule was added in v4.5.0.) | |
+| greater_than | Yes | Fails if field is less than or equal to the parameter value or not numeric. | `greater_than[8]` |
+| greater_than_equal_to | Yes | Fails if field is less than the parameter value, or not numeric. | `greater_than_equal_to[5]` |
+| hex | No | Fails if field contains anything other than hexadecimal characters. | |
+| if_exist | No | If this rule is present, validation will check the field only when the field key exists in the data to validate. | |
+| in_list | Yes | Fails if field is not within a predetermined list. | `in_list[red,blue,green]` |
+| integer | No | Fails if field contains anything other than an integer. | |
+| is_natural | No | Fails if field contains anything other than a natural number: `0`, `1`, `2`, `3`, etc. | |
+| is_natural_no_zero | No | Fails if field contains anything other than a natural number, except zero: `1`, `2`, `3`, etc. | |
+| is_not_unique | Yes | Checks the database to see if the given value exists. Can ignore records by field/value to filter (currently accepts only one filter). (Since v4.6.0, you can optionally pass the dbGroup as a parameter) | `is_not_unique[table.field,where_field,where_value]` or `is_not_unique[dbGroup.table.field,where_field,where_value]` |
+| is_unique | Yes | Checks if this field value exists in the database. Optionally set a column and value to ignore, useful when updating records to ignore itself. (Since v4.6.0, you can optionally pass the dbGroup as a parameter) | `is_unique[table.field,ignore_field,ignore_value]` or `is_unique[dbGroup.table.field,ignore_field,ignore_value]` |
+| less_than | Yes | Fails if field is greater than or equal to the parameter value or not numeric. | `less_than[8]` |
+| less_than_equal_to | Yes | Fails if field is greater than the parameter value or not numeric. | `less_than_equal_to[8]` |
+| matches | Yes | The value must match the value of the field in the parameter. | `matches[field]` |
+| max_length | Yes | Fails if field is longer than the parameter value. | `max_length[8]` |
+| min_length | Yes | Fails if field is shorter than the parameter value. | `min_length[3]` |
+| not_in_list | Yes | Fails if field is within a predetermined list. | `not_in_list[red,blue,green]` |
+| numeric | No | Fails if field contains anything other than numeric characters. | |
+| permit_empty | No | Allows the field to receive an empty array, empty string, null or false. On success, it skips all other validation rules except for required_with and required_without. | |
+| regex_match | Yes | Fails if field does not match the regular expression. **Note:** Since v4.7.0, if you're using a placeholder with this rule, you must use double braces `{{...}}` instead of single ones `{...}`. | `regex_match[/regex/]` |
+| required | No | Fails if the field is an empty array, empty string, null or false. | |
+| required_with | Yes | The field is required when any of the other fields is not [empty()](https://www.php.net/manual/en/function.empty.php) in the data. | `required_with[field1,field2]` |
+| required_without | Yes | The field is required when any of the other fields is [empty()](https://www.php.net/manual/en/function.empty.php) in the data. | `required_without[field1,field2]` |
+| string | No | A generic alternative to the **alpha**\* rules that confirms the element is a string | |
+| timezone | No | Fails if field does not match a timezone per [timezone_identifiers_list()](https://www.php.net/manual/en/function.timezone-identifiers-list.php) | |
+| valid_base64 | No | Fails if field contains anything other than valid Base64 characters. | |
+| valid_cc_number | Yes | Verifies that the credit card number matches the format used by the specified provider. Currently supported providers are: American Express (`amex`), China Unionpay (`unionpay`), Diners Club Carte Blanche (`carteblanche`), Diners Club (`dinersclub`), Discover Card (`discover`), Interpayment (`interpayment`), JCB (`jcb`), Maestro (`maestro`), Dankort (`dankort`), NSPK MIR (`mir`), Troy (`troy`), MasterCard (`mastercard`), Visa (`visa`), UATP (`uatp`), Verve (`verve`), CIBC Convenience Card (`cibc`), Royal Bank of Canada Client Card (`rbc`), TD Canada Trust Access Card (`tdtrust`), Scotiabank Scotia Card (`scotia`), BMO ABM Card (`bmoabm`), HSBC Canada Card (`hsbc`) | `valid_cc_number[amex]` |
+| valid_date | Yes | Fails if field does not contain a valid date. Any string that [strtotime()](https://www.php.net/manual/en/function.strtotime.php) accepts is valid if you don't specify an optional parameter that matches a date format. **So it is usually necessary to specify the parameter.** | `valid_date[d/m/Y]` |
+| valid_email | No | Fails if field does not contain a valid email address. | |
+| valid_emails | No | Fails if any value provided in a comma-separated list is not a valid email. | |
+| valid_ip | Yes | Fails if the supplied IP is not valid. Accepts an optional parameter of `ipv4` or `ipv6` to specify an IP format. | `valid_ip[ipv6]` |
+| valid_json | No | Fails if field does not contain a valid JSON string. | |
+| valid_url | No | Fails if field does not contain (loosely) a URL. Includes simple strings that could be hostnames, like "codeigniter". **Normally,** `valid_url_strict` **should be used.** | |
+| valid_url_strict | Yes | Fails if field does not contain a valid URL. You can optionally specify a list of valid schemes. If not specified, `http,https` are valid. This rule uses PHP's `FILTER_VALIDATE_URL`. | `valid_url_strict[https]` |
+
+!!! note "Note"
+ You can also use any native PHP functions that return boolean and permit at least one parameter, the field data to validate.
+
+!!! important "Important"
+ The Validation library **never alters the data** to validate.
+
+### Rules for File Uploads
+
+When you validate uploaded files, you must use the rules specifically created for file validation.
+
+!!! important "Important"
+ Only rules that are listed in the table below can be used to validate files. Therefore, if you add any general rules, like `permit_empty`, to a file validation rules array or string, the file validation will not work correctly.
+
+Since the value of a file upload HTML field doesn't exist, and is stored in the `$_FILES` global, the name of the input field will need to be used twice: once to specify the field name as you would for any other rule, and again as the first parameter of all file-upload-related rules:
+
+```
+// In the HTML
+
+
+// In the controller
+$this->validateData([], [
+ 'avatar' => 'uploaded[avatar]|max_size[avatar,1024]',
+]);
+```
+
+See also [File Upload Form Tutorial](uploaded_files.md#file-upload-form-tutorial).
+
+| Rule | Parameter | Description | Example |
+|----|----|----|----|
+| uploaded | Yes | Fails if the name of the parameter does not match the name of any uploaded files. If you want the file upload to be optional (not required), do not define this rule. | `uploaded[field_name]` |
+| max_size | Yes | Fails if the uploaded file is larger than the second parameter in kilobytes (kb), or if the file is larger than the allowed maximum size declared in the php.ini config file (`upload_max_filesize` directive). | `max_size[field_name,2048]` |
+| max_dims | Yes | Fails if the maximum width and height of an uploaded image exceed the values. The first parameter is the field name. The second is the width, and the third is the height. Will also fail if the file cannot be determined to be an image or the upload failed with an error other than `UPLOAD_ERR_NO_FILE`. | `max_dims[field_name,300,150]` |
+| min_dims | Yes | Fails if the minimum width and height of an uploaded image do not meet values. The first parameter is the field name. The second is the width, and the third is the height. Will also fail if the file cannot be determined to be an image or the upload failed with an error other than `UPLOAD_ERR_NO_FILE`. (This rule was added in v4.6.0.) | `min_dims[field_name,300,150]` |
+| mime_in | Yes | Fails if the file's mime type is not one listed in the parameters. | `mime_in[field_name,image/png,image/jpeg]` |
+| ext_in | Yes | Fails if the client filename's extension is not one listed in the parameters, or if the detected MIME type does not match the MIME types associated with the extension. | `ext_in[field_name,png,jpg,gif]` |
+| is_image | Yes | Fails if the file cannot be determined to be an image based on the mime type. | `is_image[field_name]` |
+
+The file validation rules apply to both single and multiple file uploads.
+
+The `is_image`, `mime_in`, and `ext_in` rules check the uploaded file and its client filename, but they cannot guarantee that a web server will not execute a file saved under that name. See [Moving Files](uploaded_files.md#moving-files) for safe storage practices.
diff --git a/docs/license.md b/docs/license.md
new file mode 100644
index 000000000000..f9ee39b8ad9e
--- /dev/null
+++ b/docs/license.md
@@ -0,0 +1,10 @@
+# The MIT License (MIT)
+
+Copyright (c) 2014-2019 British Columbia Institute of Technology
+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 in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/docs/models/entities.md b/docs/models/entities.md
new file mode 100644
index 000000000000..3c9218f309a0
--- /dev/null
+++ b/docs/models/entities.md
@@ -0,0 +1,723 @@
+# Using Entity Classes
+
+CodeIgniter supports Entity classes as a first-class citizen, while keeping them completely optional to use. They are commonly used as part of the Repository pattern, but can be used directly with the [Model](model.md) if that fits your needs better.
+
+## Entity Usage
+
+At its core, an Entity class is simply a class that represents a single database row. It has class properties to represent the database columns, and provides any additional methods to implement the business logic for that row.
+
+!!! note "Note"
+ For ease of understanding, the explanation here is based on the case of using a database. However, Entity can also be used for data that does not come from a database.
+
+The core feature, though, is that it doesn't know anything about how to persist itself. That's the responsibility of the model or the repository class. That way, if anything changes in how you need to save the object, you don't have to change how that object is used throughout the application.
+
+This makes it possible to use JSON or XML files to store the objects during a rapid prototyping stage, and then easily switch to a database when you've proven the concept works.
+
+Let's walk through a very simple User Entity and how we'd work with it to help make things clear.
+
+Assume you have a database table named `users` that has the following schema:
+
+```
+id - integer
+username - string
+email - string
+password - string
+created_at - datetime
+```
+
+### Create the Entity Class
+
+Now create a new Entity class. Since there's no default location to store these classes, and it doesn't fit in with the existing directory structure, create a new directory at **app/Entities**. Create the Entity itself at **app/Entities/User.php**.
+
+```php
+find($id);
+
+// Display
+echo $user->username;
+echo $user->email;
+
+// Updating
+unset($user->username);
+
+if (! isset($user->username)) {
+ $user->username = 'something new';
+}
+
+$userModel->save($user);
+
+// Create
+$user = new \App\Entities\User();
+$user->username = 'foo';
+$user->email = 'foo@example.com';
+$userModel->save($user);
+```
+
+You may have noticed that the `User` class has not set any properties for the columns, but you can still access them as if they were public properties. The base class, `CodeIgniter\Entity\Entity`, takes care of this for you, as well as providing the ability to check the properties with `isset()`, `unset()` them, and keep track of what columns have changed since the object was created or pulled from the database.
+
+!!! note "Note"
+ The Entity class stores the data in the class property `$attributes` internally.
+
+When the User is passed to the model's `save()` method, it automatically takes care of reading the properties and saving any changes to columns listed in the model's `$allowedFields` property. It also knows whether to create a new row, or update an existing one.
+
+!!! note "Note"
+ When we call the `insert()` method, all the values from the Entity are passed to the method, but when we call the `update()` method, only the values that have changed are passed.
+
+### Filling Properties Quickly
+
+The Entity class also provides a method, `fill()`, that allows you to shove an array of key/value pairs into the class and populate the class properties. Any property in the array will be set on the Entity. However, when saving through the model, only the fields in `$allowedFields` will actually be saved to the database, so you can store additional data on your entities without worrying much about stray fields getting saved incorrectly.
+
+```php
+request->getPost();
+
+$user = new \App\Entities\User();
+$user->fill($data);
+$userModel->save($user);
+```
+
+You can also pass the data in the constructor and the data will be passed through the `fill()` method during instantiation.
+
+```php
+request->getPost();
+
+$user = new \App\Entities\User($data);
+$userModel->save($user);
+```
+
+### Bulk Accessing Properties
+
+The Entity class has two methods to extract all available properties into an array: `toArray()` and `toRawArray()`. Using the raw version will bypass magic "getter" methods and casts. Both methods can take a boolean first parameter to specify whether returned values should be filtered by those that have changed, and a boolean final parameter to make the method recursive, in case of nested Entities.
+
+### Hidden properties
+
+An Entity may have hidden attributes - their names start with an underscore (`_`). By default, these hidden properties are not included when you call `toArray()`. However, you can still access them directly if needed.
+
+If you want hidden properties to appear in the `toArray()` output, you'll need to use the `datamap` feature - it makes those properties "visible".
+
+Keep in mind that the smart `__get()` and `__set()` methods (described in the next section) ignore leading underscores in property names. This means that two attributes with the same name (one starting with `_` and one without) will both use the same getter and setter, so you have to choose which one to handle by default.
+
+```php
+ '_role',
+ ];
+
+ protected $attributes = [
+ '__secure' => 'On',
+ '_role' => 'user',
+ 'about' => '',
+ ];
+}
+
+$user = new User(['__secure' => 'Off', 'about' => 'Hi, I am John!', '_role' => 'admin']);
+
+echo 'Secure: ' . $user->__secure;
+print_r($user->toArray());
+print_r($user->toRawArray());
+
+/**
+ * Output:
+ *
+ * Secure: Off
+ * Array
+ * (
+ * [about] => Hi, I am John!
+ * [_role] => admin
+ * )
+ * Array
+ * (
+ * [__secure] => Off
+ * [_role] => admin
+ * [about] => Hi, I am John!
+ * )
+ */
+```
+
+## Handling Business Logic
+
+While the examples above are convenient, they don't help enforce any business logic. The base Entity class implements some smart `__get()` and `__set()` methods that will check for special methods and use those instead of using the attributes directly, allowing you to enforce any business logic or data conversion that you need.
+
+Here's an updated User entity to provide some examples of how this could be used:
+
+```php
+attributes['password'] = password_hash($pass, PASSWORD_BCRYPT);
+
+ return $this;
+ }
+
+ public function setCreatedAt(string $dateString)
+ {
+ $this->attributes['created_at'] = new Time($dateString, 'UTC');
+
+ return $this;
+ }
+
+ public function getCreatedAt(string $format = 'Y-m-d H:i:s')
+ {
+ // Convert to CodeIgniter\I18n\Time object
+ $this->attributes['created_at'] = $this->mutateDate($this->attributes['created_at']);
+
+ $timezone = $this->timezone ?? app_timezone();
+
+ $this->attributes['created_at']->setTimezone($timezone);
+
+ return $this->attributes['created_at']->format($format);
+ }
+}
+```
+
+The first thing to notice is the name of the methods we've added. For each one, the class expects the snake_case column name to be converted into PascalCase, and prefixed with either `set` or `get`. These methods will then be automatically called whenever you set or retrieve the class property using the direct syntax (i.e., `$user->email`). The methods do not need to be public unless you want them accessed from other classes. For example, the `created_at` class property will be accessed through the `setCreatedAt()` and `getCreatedAt()` methods.
+
+!!! note "Note"
+ This only works when trying to access the properties from outside of the class. Any methods internal to the class must call the `setX()` and `getX()` methods directly.
+
+In the `setPassword()` method we ensure that the password is always hashed.
+
+In `setCreatedAt()` we convert the string we receive from the model into a DateTime object, ensuring that our timezone is UTC so we can easily convert to the viewer's current timezone. In `getCreatedAt()`, it converts the time to a formatted string in the application's current timezone.
+
+While fairly simple, these examples show that using Entity classes can provide a very flexible way to enforce business logic and create objects that are pleasant to use.
+
+```php
+password = 'my great password';
+$user->setPassword('my great password');
+```
+
+### Special Getter/Setter
+
+!!! success "Available from version 4.4.0"
+
+For example, if your Entity's parent class already has a `getParent()` method defined, and your Entity also has a column named `parent`, when you try to add business logic to the `getParent()` method in your Entity class, the method is already defined.
+
+In such a case, you can use the special getter/setter. Instead of `getX()`/`setX()`, set `_getX()`/`_setX()`.
+
+In the above example, if your Entity has the `_getParent()` method, the method will be used when you get `$entity->parent`, and the `_setParent()` method will be used when you set `$entity->parent`.
+
+## Data Mapping
+
+At many points in your career, you will run into situations where the use of an application has changed and the original column names in the database no longer make sense. Or you find that your coding style prefers camelCase class properties, but your database schema requires snake_case names. These situations can be easily handled with the Entity class' data mapping features.
+
+As an example, imagine you have the simplified User Entity that is used throughout your application:
+
+```php
+ null,
+ 'name' => null, // Represents a username
+ 'email' => null,
+ 'password' => null,
+ 'created_at' => null,
+ 'updated_at' => null,
+ ];
+}
+```
+
+Your boss comes to you and says that no one uses usernames anymore, so you're switching to just use emails for login. But they do want to personalize the application a bit, so they want you to change the `name` field to represent a user's full name now, not their username like it does currently. To keep things tidy and ensure things continue making sense in the database, you whip up a migration to rename the `name` field to `full_name` for clarity.
+
+Ignoring how contrived this example is, we now have two choices on how to fix the User class. We could modify the class property from `$name` to `$full_name`, but that would require changes throughout the application. Instead, we can simply map the `full_name` column in the database to the `$name` property, and be done with the Entity changes:
+
+```php
+ null,
+ 'full_name' => null, // In the $attributes, the key is the db column name
+ 'email' => null,
+ 'password' => null,
+ 'created_at' => null,
+ 'updated_at' => null,
+ ];
+
+ protected $datamap = [
+ // property_name => db_column_name
+ 'name' => 'full_name',
+ ];
+}
+```
+
+By adding our new database name to the `$datamap` array, we can tell the class what class property the database column should be accessible through. The key of the array is the class property to map it to, and the value in the array is the name of the column in the database.
+
+In this example, when the model sets the `full_name` field on the User class, it actually assigns that value to the class' `$name` property, so it can be set and retrieved through `$user->name`. The value will also still be accessible through the original `$user->full_name`, as this is needed for the model to get the data back out and save it to the database. However, `unset()` and `isset()` only work on the mapped property, `$user->name`, not on the database column name, `$user->full_name`.
+
+!!! note "Note"
+ When you use Data Mapping, you must define `set*()` and `get*()` methods for the database column name. In this example, you must define `setFullName()` and `getFullName()`.
+
+## Mutators
+
+### Date Mutators
+
+By default, the Entity class will convert fields named `created_at`, `updated_at`, or `deleted_at` into [Time](../libraries/time.md) instances whenever they are set or retrieved. The Time class provides a large number of helpful methods in an immutable, localized way.
+
+You can define which properties are automatically converted by adding the name to the `$dates` property:
+
+```php
+created_at = 'April 15, 2017 10:30:00';
+
+// Can now use any Time methods:
+echo $user->created_at->humanize();
+echo $user->created_at->setTimezone('Europe/London')->toDateString();
+```
+
+### Property Casting
+
+You can specify that properties in your Entity should be converted to common data types with the `$casts` property. This option should be an array where the key is the name of the class property, and the value is the data type it should be cast to.
+
+Property casting affects both read (get) and write (set), but some types affect only read (get).
+
+#### Scalar Type Casting
+
+Properties can be cast to any of the following data types: **integer**, **float**, **double**, **string**, **boolean**, **object**, **array**, **datetime**, **timestamp**, **uri**, **int-bool** and **enum**. Add a question mark at the beginning of the type to mark the property as nullable, i.e., **?string**, **?integer**.
+
+!!! note "Note"
+ **int-bool** can be used since v4.3.0.
+
+!!! note "Note"
+ **enum** can be used since v4.7.0.
+
+For example, if you had a User entity with an `is_banned` property, you can cast it as a boolean:
+
+```php
+ 'boolean',
+ 'is_banned_nullable' => '?boolean',
+ ];
+}
+```
+
+#### Array/Json Casting
+
+Array/Json casting is especially useful with fields that store serialized arrays or JSON. When you read the property's value:
+
+- with the **array** cast, the value is automatically unserialized.
+- with the **json** cast, the value is automatically converted with `json_decode($value, false)`.
+- with the **json-array** cast, the value is automatically converted with `json_decode($value, true)`.
+
+Unlike the rest of the data types that you can cast properties into, these cast types also convert the value whenever the property is set:
+
+- the **array** cast serializes the value.
+- the **json** and **json-array** casts use `json_encode()` on the value.
+
+```php
+ 'array',
+ 'options_object' => 'json',
+ 'options_array' => 'json-array',
+ ];
+}
+```
+
+```php
+find(15);
+$options = $user->options;
+
+$options['foo'] = 'bar';
+
+$user->options = $options;
+$userModel->save($user);
+```
+
+!!! warning "Warning"
+ The `array` cast uses PHP serialization. Only read values previously written through the same Entity cast. Never store untrusted serialized strings directly in an `array`-cast field.
+
+#### CSV Casting
+
+If you know you have a flat array of simple values, encoding them as a serialized or JSON string may be more complex than the original structure. Casting as Comma-Separated Values (CSV) is a simpler alternative that will result in a string that uses less space and is more easily read by humans:
+
+```php
+ 'csv',
+ ];
+}
+```
+
+Stored in the database as "red,yellow,green":
+
+```php
+colors = ['red', 'yellow', 'green'];
+```
+
+!!! note "Note"
+ Casting as CSV uses PHP's internal `implode` and `explode` methods and assumes all values are string-safe and free of commas. For more complex data casts try `array` or `json`.
+
+#### Enum Casting
+
+!!! success "Available from version 4.7.0"
+
+You can cast properties to PHP enums. You must specify the enum class name as a parameter.
+
+Enum casting supports:
+
+- **Backed enums** (string or int) - The backing value is stored in the database
+- **Unit enums** - The case name is stored in the database as a string
+
+For example, if you had a User entity with a `status` property using a backed enum:
+
+```php
+ 'enum[App\Enums\UserStatus]',
+ ];
+}
+```
+
+Now, when you access the `status` property, it will automatically be converted to a `UserStatus` enum instance:
+
+```php
+find(1);
+
+// Returns a UserStatus enum instance
+echo $user->status->value; // 'active'
+
+// Set using enum
+$user->status = UserStatus::Inactive;
+
+// Or set using the backing value (will be converted to enum on read)
+$user->status = 'pending';
+
+// Note: Internally, enums are always stored as their backing value (string/int)
+// in the entity's $attributes array
+```
+
+For nullable enums:
+
+```php
+ '?enum[App\Enums\UserStatus]',
+ ];
+}
+```
+
+#### Custom Casting
+
+You can define your own conversion types for getting and setting data.
+
+First, you need to create a handler class for your type. Let's say the class will be located in the **app/Entities/Cast** directory:
+
+```php
+ 'base64',
+ ];
+
+ // Bind the type to the handler
+ protected $castHandlers = [
+ 'base64' => Cast\CastBase64::class,
+ ];
+}
+
+// ...
+
+$entity->key = 'test'; // dGVzdA==
+echo $entity->key; // test
+```
+
+If you don't need to change values when getting or setting a value, just don't implement the appropriate method:
+
+```php
+ 'class[App\SomeClass, param2, param3]',
+ ];
+
+ // Bind the type to the handler
+ protected $castHandlers = [
+ 'class' => 'SomeHandler',
+ ];
+}
+```
+
+```php
+
+ * string(13) "App\SomeClass"
+ * [1]=>
+ * string(6) "param2"
+ * [2]=>
+ * string(6) "param3"
+ * }
+ */
+ }
+}
+```
+
+!!! note "Note"
+ If the casting type is marked as nullable like `?bool` and the passed value is not null, then the parameter with the value `nullable` will be passed to the casting type handler. If the casting type has predefined parameters, then `nullable` will be added to the end of the list.
+
+## Checking for Changed Attributes
+
+You can check if an Entity attribute has changed since it was created. The only parameter is the name of the attribute to check:
+
+```php
+hasChanged('name'); // false
+
+$user->name = 'Fred';
+$user->hasChanged('name'); // true
+```
+
+Or, to check the whole entity for changed values, omit the parameter:
+
+```php
+hasChanged(); // true
+```
+
+### Deep Change Tracking
+
+!!! success "Available from version 4.7.0"
+
+The Entity class performs **deep comparison** for objects and arrays to accurately detect changes in their internal state.
+
+#### Scalar Values
+
+For scalar values (strings, integers, floats, booleans, null), the Entity uses direct comparison. When all attributes in an Entity are scalars, an optimized comparison is used for better performance.
+
+#### Objects and Arrays
+
+For objects and arrays, the Entity JSON-encodes and normalizes the values for comparison. This means that modifications to nested structures, object properties, array elements, nested entities (using `toRawArray()`), enums (`BackedEnum` and `UnitEnum`), datetime objects (`DateTimeInterface`), collections (`Traversable`), value objects with `__toString()`, and objects implementing `JsonSerializable` or `toArray()` will be properly detected.
diff --git a/docs/models/index.md b/docs/models/index.md
new file mode 100644
index 000000000000..e9b8036685c1
--- /dev/null
+++ b/docs/models/index.md
@@ -0,0 +1,6 @@
+# Modeling Data
+
+CodeIgniter comes with rich tools for modeling and working with your database tables and records.
+
+- [Using CodeIgniter's Model](model.md)
+- [Using Entity Classes](entities.md)
diff --git a/docs/models/model.md b/docs/models/model.md
new file mode 100644
index 000000000000..0c6117397743
--- /dev/null
+++ b/docs/models/model.md
@@ -0,0 +1,1545 @@
+# Using CodeIgniter's Model
+
+## Models
+
+CodeIgniter's Model provides convenience features and additional functionality that people commonly use to make working with a **single table** in your database more convenient.
+
+It comes out of the box with helper methods for many of the standard ways you would need to interact with a database table, including finding records, updating records, deleting records, and more.
+
+## Accessing Models
+
+Models are typically stored in the **app/Models** directory. They should have a namespace that matches their location within the directory, like `namespace App\Models`.
+
+You can access models within your classes by creating a new instance or using the `model()` helper function.
+
+```php
+allowedFields[] = 'middlename';
+ }
+}
+```
+
+### Connecting to the Database
+
+When the class is first instantiated, if no database connection instance is passed to the constructor, and if you don't set the `$DBGroup` property on your model class, it will automatically connect to the default database group, as set in the database configuration.
+
+You can modify which group is used on a per-model basis by adding the `$DBGroup` property to your class. This ensures that within the model any references to `$this->db` are made through the appropriate connection.
+
+```php
+ [Entity] --- (2) --> [Database]
+ [App Code] <-- (4) --- [Entity] <-- (3) --- [Database]
+ ```
+
+ When using this casting, the Entity will have correctly typed PHP values in the attributes. This behavior is completely different from the previous behavior. Do not expect the attributes to hold raw data from the database.
+
+### Defining Data Types
+
+The `$casts` property sets its definition. This option should be an array where the key is the name of the field, and the value is the data type:
+
+```php
+ 'int',
+ 'birthdate' => '?datetime',
+ 'hobbies' => 'json-array',
+ 'active' => 'int-bool',
+ ];
+ // ...
+}
+```
+
+### Data Types
+
+The following types are provided by default. Add a question mark at the beginning of the type to mark the field as nullable, i.e., `?int`, `?datetime`.
+
+| Type | PHP Value Type | DB Column Type |
+|--------------|----------------|---------------------------|
+| `int` | int | int type |
+| `float` | float | float (numeric) type |
+| `bool` | bool | bool/int/string type |
+| `int-bool` | bool | int type (1 or 0) |
+| `array` | array | string type (serialized) |
+| `csv` | array | string type (CSV) |
+| `json` | stdClass | json/string type |
+| `json-array` | array | json/string type |
+| `datetime` | Time | datetime type |
+| `timestamp` | Time | int type (UNIX timestamp) |
+| `uri` | URI | string type |
+| `enum` | Enum | string/int type |
+
+#### csv
+
+Casting as `csv` uses PHP's internal `implode()` and `explode()` functions and assumes all values are string-safe and free of commas. For more complex data casts try `array` or `json`.
+
+#### datetime
+
+You can pass a parameter like `datetime[ms]` for date/time with milliseconds, or `datetime[us]` for date/time with microseconds.
+
+The datetime format is set in the `dateFormat` array of the [database configuration](../database/configuration.md#description-of-values) in the **app/Config/Database.php** file.
+
+!!! note "Note"
+ When you set `ms` or `us` as a parameter, **Model** takes care of the fractional seconds of the Time. But **Query Builder** does not. So you still need to use the `format()` method when you pass the Time to Query Builder's methods like `where()`:
+
+ ```php
+ $model = model('SomeModel');
+
+ $now = \CodeIgniter\I18n\Time::now();
+
+ // The following code passes the microseconds to Query Builder.
+ $model->where('my_dt_field', $now->format('Y-m-d H:i:s.u'))->findAll();
+ // Generates: SELECT * FROM `my_table` WHERE `my_dt_field` = '2024-07-28 18:57:58.900326'
+
+ // But the following code loses the microseconds.
+ $model->where('my_dt_field', $now)->findAll();
+ // Generates: SELECT * FROM `my_table` WHERE `my_dt_field` = '2024-07-28 18:57:58'
+ ```
+
+!!! note "Note"
+ Prior to v4.6.0, you could not use `ms` or `us` as a parameter, because the fractional seconds of Time were lost due to bugs.
+
+#### timestamp
+
+The timezone of the `Time` instance created will be the default timezone (app's timezone), not UTC.
+
+#### enum
+
+!!! success "Available from version 4.7.0"
+
+You can cast fields to PHP enums. You must specify the enum class name as a parameter, like `enum[App\Enums\StatusEnum]`.
+
+Enum casting supports:
+
+- **Backed enums** (string or int) - The backing value is stored in the database
+- **Unit enums** - The case name is stored in the database as a string
+
+### Custom Casting
+
+You can define your own conversion types.
+
+#### Creating Custom Handlers
+
+First, you need to create a handler class for your type. Let's say the class will be located in the **app/Models/Cast** directory:
+
+```php
+ 'base64',
+ ];
+
+ // Bind the type to the handler
+ protected array $castHandlers = [
+ 'base64' => CastBase64::class,
+ ];
+
+ // ...
+}
+```
+
+#### Parameters
+
+In some cases, one type is not enough. In this situation, you can use additional parameters. Additional parameters are indicated in square brackets and listed with a comma like `type[param1, param2]`.
+
+```php
+ 'class[App\SomeClass, param2, param3]',
+ ];
+
+ // Bind the type to the handler
+ protected array $castHandlers = [
+ 'class' => SomeHandler::class,
+ ];
+
+ // ...
+}
+```
+
+```php
+
+ * string(13) "App\SomeClass"
+ * [1]=>
+ * string(6) "param2"
+ * [2]=>
+ * string(6) "param3"
+ * }
+ */
+ }
+}
+```
+
+!!! note "Note"
+ If the casting type is marked as nullable like `?bool` and the passed value is not null, then the parameter with the value `nullable` will be passed to the casting type handler. If the casting type has predefined parameters, then `nullable` will be added to the end of the list.
+
+## Working with Data
+
+### Finding Data
+
+Several functions are provided for doing basic CRUD work on your tables, including `find()`, `insert()`, `update()`, `delete()` and more.
+
+#### find()
+
+Returns a single row where the primary key matches the value passed in as the first parameter:
+
+```php
+find($userId);
+```
+
+The value is returned in the format specified in [$returnType](#returntype).
+
+You can specify more than one row to return by passing an array of primaryKey values instead of just one:
+
+```php
+find([1, 2, 3]);
+```
+
+!!! note "Note"
+ If `find()` is called without parameters or with `null`, it will return all rows in that model's table, effectively acting like `findAll()`, though less explicit.
+
+#### findColumn()
+
+Returns null or an indexed array of column values:
+
+```php
+findColumn($columnName);
+```
+
+`$columnName` should be the name of a single column. Otherwise, you will get a `DataException`.
+
+#### findAll()
+
+Returns all results:
+
+```php
+findAll();
+```
+
+This query may be modified by interjecting Query Builder commands as needed prior to calling this method:
+
+```php
+where('active', 1)->findAll();
+```
+
+You can pass in limit and offset values as the first and second parameters, respectively:
+
+```php
+findAll($limit, $offset);
+```
+
+#### first()
+
+Returns the first row in the result set. This is best used in combination with the query builder.
+
+```php
+where('deleted', 0)->first();
+```
+
+#### withDeleted()
+
+If [$useSoftDeletes](#usesoftdeletes) is true, then the **find\*()** methods will not return any rows where `deleted_at IS NOT NULL`. To temporarily override this, you can use the `withDeleted()` method prior to calling the **find\*()** method.
+
+```php
+findAll();
+
+// Gets all rows
+$allUsers = $userModel->withDeleted()->findAll();
+```
+
+#### onlyDeleted()
+
+Whereas `withDeleted()` will return both deleted and not-deleted rows, this method modifies the next **find\*()** methods to return only soft deleted rows:
+
+```php
+onlyDeleted()->findAll();
+```
+
+### Saving Data
+
+#### insert()
+
+The first parameter is an associative array of data to create a new row of data in the database. If an object is passed instead of an array, it will attempt to convert it to an array.
+
+The array's keys must match the name of the columns in the [$table](#table), while the array's values are the values to save for that key.
+
+The optional second parameter is of type boolean, and if it is set to false, the method will return a boolean value, which indicates the success or failure of the query.
+
+You can retrieve the last inserted row's primary key using the `getInsertID()` method.
+
+```php
+ 'darth',
+ 'email' => 'd.vader@theempire.com',
+];
+
+// Inserts data and returns inserted row's primary key
+$userModel->insert($data);
+
+// Inserts data and returns true on success and false on failure
+$userModel->insert($data, false);
+
+// Returns inserted row's primary key
+$userModel->getInsertID();
+```
+
+#### allowEmptyInserts()
+
+!!! success "Available from version 4.3.0"
+
+You can use the `allowEmptyInserts()` method to insert empty data. By default, the Model throws an exception when you try to insert empty data. But if you call this method, the check will no longer be performed.
+
+```php
+allowEmptyInserts()->insert([]);
+```
+
+You may also change this setting with the [$allowEmptyInserts](#allowemptyinserts) property.
+
+You can enable the check again by calling `allowEmptyInserts(false)`.
+
+#### update()
+
+Updates an existing record in the database. The first parameter is the [$primaryKey](#primarykey) of the record to update. An associative array of data is passed into this method as the second parameter. The array's keys must match the name of the columns in the [$table](#table), while the array's values are the values to save for that key:
+
+```php
+ 'darth',
+ 'email' => 'd.vader@theempire.com',
+];
+
+$userModel->update($id, $data);
+```
+
+!!! important "Important"
+ Since v4.3.0, this method raises a `DatabaseException` if it generates an SQL statement without a WHERE clause. In previous versions, if it was called without [$primaryKey](#primarykey) specified and an SQL statement was generated without a WHERE clause, the query would still execute and all records in the table would be updated.
+
+Multiple records may be updated with a single call by passing an array of primary keys as the first parameter:
+
+```php
+ 1,
+];
+
+$userModel->update([1, 2, 3], $data);
+```
+
+When you need a more flexible solution, you can leave the parameters empty, and it functions like the Query Builder's update command, with the added benefit of validation, events, etc:
+
+```php
+whereIn('id', [1, 2, 3])
+ ->set(['active' => 1])
+ ->update();
+```
+
+#### replace()
+
+Pass an associative array to `replace($data)` to perform the Query Builder's `CodeIgniter\Database\BaseBuilder::replace()` operation. The model validates the supplied data and handles timestamps, but does not trigger model callbacks.
+
+Since v4.7.6, fields outside [$allowedFields](#allowedfields) are discarded when field protection is enabled, except for a supplied [$primaryKey](#primarykey). Supplied timestamp values not listed in [$allowedFields](#allowedfields) are discarded and, when timestamps are enabled, replaced by generated ones. Pass `true` as the second argument to return the SQL instead of executing it.
+
+If field protection removes all data from a non-empty input, or leaves only its primary key after removing other fields, `replace()` throws a `DataException` before generating timestamps. Explicitly passing `null` or an empty array still allows the method to use data previously set on the Query Builder.
+
+!!! warning "Warning"
+ Field protection does not authorize access to a record. A supplied primary key is retained even for auto-incrementing keys, so untrusted input can select the record to replace. Use a trusted identifier and verify that the caller is authorized to modify the target record; do not rely on `$allowedFields` alone when passing request data to `replace()`.
+
+!!! important "Important"
+ Field protection filters input; it does not guarantee that existing values of omitted columns are preserved. Depending on the database driver, replacement may delete the existing row and insert a new one, using defaults for omitted columns. Use `update()` to change selected fields of an existing row.
+
+Fields used to match existing rows must be in `$allowedFields` or be the primary key. Filtering a unique column can change whether a replacement matches an existing row. With PostgreSQL, the first remaining field is used for matching; ensure that it is a primary or unique key to avoid matching multiple rows.
+
+#### save()
+
+This is a wrapper around the `insert()` and `update()` methods that handles inserting or updating the record automatically, based on whether it finds an array key matching the **primary key** value:
+
+```php
+ 'darth',
+ 'email' => 'd.vader@theempire.com',
+];
+
+$userModel->save($data);
+
+// Performs an update, since the primary key, 'id', is found.
+$data = [
+ 'id' => 3,
+ 'username' => 'darth',
+ 'email' => 'd.vader@theempire.com',
+];
+$userModel->save($data);
+```
+
+The save method can also make working with custom class result objects much simpler by recognizing a non-simple object and grabbing its public and protected values into an array, which is then passed to the appropriate insert or update method. This allows you to work with Entity classes in a very clean way. Entity classes are simple classes that represent a single instance of an object type, like a user, a blog post, a job, etc. This class is responsible for maintaining the business logic surrounding the object itself, like formatting elements in a certain way, etc. They shouldn't have any idea about how they are saved to the database. At their simplest, they might look like this:
+
+```php
+{$key};
+ }
+ }
+
+ public function __set($key, $value)
+ {
+ if (property_exists($this, $key)) {
+ $this->{$key} = $value;
+ }
+ }
+}
+```
+
+A very simple model to work with this might look like:
+
+```php
+find(15);
+
+// Make some changes
+$job->name = 'Foobar';
+
+// Save the changes
+$model->save($job);
+```
+
+!!! note "Note"
+ If you find yourself working with Entities a lot, CodeIgniter provides a built-in [Entity class](entities.md) that provides several handy features that make developing Entities simpler.
+
+#### Saving Dates
+
+!!! success "Available from version 4.5.0"
+
+When saving data, if you pass [Time](../libraries/time.md) instances, they are converted to strings with the format defined in `dateFormat['datetime']` and `dateFormat['date']` in the [database configuration](../database/configuration.md#description-of-values).
+
+!!! note "Note"
+ Prior to v4.5.0, the date/time formats were hard coded as `Y-m-d H:i:s` and `Y-m-d` in the Model class.
+
+#### Primary Key Validation
+
+!!! success "Available from version 4.7.0"
+
+The `insert()`, `insertBatch()` (when [$useAutoIncrement](#useautoincrement) is `false`), `update()`, and `delete()` methods validate primary key values before executing database queries. Invalid values such as `null`, `0`, `'0'`, empty strings, booleans, empty arrays, or nested arrays will throw an `InvalidArgumentException` with a specific error message.
+
+If you need to customize this behavior (e.g., to allow `0` as a valid primary key for legacy systems), you can override the `validateID()` method in your model.
+
+### Deleting Data
+
+#### delete()
+
+Takes a primary key value as the first parameter and deletes the matching record from the model's table:
+
+```php
+delete(12);
+```
+
+If the model's [$useSoftDeletes](#usesoftdeletes) value is true, this will update the row to set `deleted_at` to the current date and time. You can force a permanent delete by setting the second parameter as true.
+
+An array of primary keys can be passed in as the first parameter to delete multiple records at once:
+
+```php
+delete([1, 2, 3]);
+```
+
+If no parameters are passed in, it will act like the Query Builder's delete method, requiring a prior where call:
+
+```php
+where('id', 12)->delete();
+```
+
+#### purgeDeleted()
+
+Cleans out the database table by permanently removing all rows that have 'deleted_at IS NOT NULL'.
+
+```php
+purgeDeleted();
+```
+
+### In-Model Validation
+
+!!! warning "Warning"
+ In-Model validation is performed just before data is stored in the database. Prior to that point, the data has not yet been validated. Processing user-input data prior to validation may introduce vulnerabilities.
+
+#### Validating Data
+
+The Model class provides a way to automatically have all data validated prior to saving to the database with the `insert()`, `update()`, or `save()` methods.
+
+!!! important "Important"
+ When you update data, by default, the validation in the model class only validates provided fields. This is to avoid validation errors when updating only some fields.
+
+ However, this means that not all validation rules you set will be checked during updates. Thus, incomplete data may pass the validation.
+
+ For example, `required*` rules or the `is_unique` rule, which require the values of other fields, may not work as expected.
+
+ To avoid such glitches, this behavior can be changed by configuration. See [$cleanValidationRules](#cleanvalidationrules) for details.
+
+#### Setting Validation Rules
+
+The first step is to fill out the [$validationRules](#validationrules) class property with the fields and rules that should be applied.
+
+!!! note "Note"
+ You can see the list of built-in Validation rules in [Available Rules](../libraries/validation.md#available-rules).
+
+If you have custom error messages that you want to use, place them in the [$validationMessages](#validationmessages) array:
+
+```php
+ 'required|max_length[30]|alpha_numeric_space|min_length[3]',
+ 'email' => 'required|max_length[254]|valid_email|is_unique[users.email]',
+ 'password' => 'required|max_length[255]|min_length[8]',
+ 'pass_confirm' => 'required_with[password]|max_length[255]|matches[password]',
+ ];
+ protected $validationMessages = [
+ 'email' => [
+ 'is_unique' => 'Sorry. That email has already been taken. Please choose another.',
+ ],
+ ];
+}
+```
+
+If you'd rather organize your rules and error messages within the [Validation Config File](../libraries/validation.md#saving-sets-of-validation-rules-to-the-config-file), you can do that and simply set [$validationRules](#validationrules) to the name of the validation rule group you created:
+
+```php
+setValidationRule($fieldName, $fieldRules);
+```
+
+#### setValidationRules($validationRules)
+
+- **Parameters**
+ - **$validationRules** `array`
+
+This function will set the validation rules.
+
+Usage example:
+
+```php
+ 'required|max_length[30]|alpha_numeric_space|min_length[3]',
+ 'email' => [
+ 'rules' => 'required|max_length[254]|valid_email|is_unique[users.email]',
+ 'errors' => [
+ 'required' => 'We really need your email.',
+ ],
+ ],
+];
+$model->setValidationRules($validationRules);
+```
+
+The other way to set the validation messages for fields is by using functions:
+
+#### setValidationMessage($field, $fieldMessages)
+
+- **Parameters**
+ - **$field** `string`
+ - **$fieldMessages** `array`
+
+This function will set the field-wise error messages.
+
+Usage example:
+
+```php
+ 'Your name is required here',
+];
+$model->setValidationMessage($fieldName, $fieldValidationMessage);
+```
+
+#### setValidationMessages($validationMessages)
+
+- **Parameters**
+ - **$validationMessages** `array`
+
+This function will set the field messages.
+
+Usage example:
+
+```php
+ [
+ 'required' => 'Your baby name is missing.',
+ 'min_length' => 'Too short, man!',
+ ],
+];
+$model->setValidationMessages($fieldValidationMessage);
+```
+
+#### Getting Validation Result
+
+Now, whenever you call the `insert()`, `update()`, or `save()` methods, the data will be validated. If it fails, the model will return boolean **false**.
+
+#### Getting Validation Errors
+
+You can use the `errors()` method to retrieve the validation errors:
+
+```php
+save($data) === false) {
+ return view('updateUser', ['errors' => $model->errors()]);
+}
+```
+
+This returns an array with the field names and their associated errors that can be used to either show all of the errors at the top of the form, or to display them individually:
+
+```php
+
+
+ $error): ?>
+
= esc($error) ?>
+
+
+
+```
+
+#### Retrieving Validation Rules
+
+You can retrieve a model's validation rules by accessing its `validationRules` property:
+
+```php
+validationRules;
+```
+
+You can also retrieve just a subset of those rules by calling the accessor method directly, with options:
+
+```php
+getValidationRules($options);
+```
+
+The `$options` parameter is an associative array with one element, whose key is either `'except'` or `'only'`, and which has as its value an array of field names of interest:
+
+```php
+getValidationRules(['except' => ['username']]);
+// get the rules for only the "city" and "state" fields
+$rules = $model->getValidationRules(['only' => ['city', 'state']]);
+```
+
+#### Validation Placeholders
+
+The model provides a simple method to replace parts of your rules based on data that's being passed into it. This sounds fairly obscure but can be especially handy with the `is_unique` validation rule. Placeholders are simply the name of the field (or array key) that was passed in as `$data` surrounded by curly brackets. It will be replaced by the **value** of the matched incoming field. An example should clarify this:
+
+```php
+ 'max_length[19]|is_natural_no_zero',
+ 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]',
+ ];
+}
+```
+
+!!! note "Note"
+ Since v4.3.5, you must set the validation rules for the placeholder field (`id`).
+
+This set of rules states that the email address should be unique in the database, except for the row that has an id matching the placeholder's value. Assuming that the form POST data had the following:
+
+```php
+ 4,
+ 'email' => 'foo@example.com',
+];
+```
+
+then the `{id}` placeholder would be replaced with the number **4**, giving this revised rule:
+
+```php
+ 'max_length[19]|is_natural_no_zero',
+ 'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,4]',
+ ];
+}
+```
+
+So it will ignore the row in the database that has `id=4` when it verifies the email is unique.
+
+!!! note "Note"
+ Since v4.3.5, if the placeholder (`id`) value does not pass the validation, the placeholder will not be replaced.
+
+This can also be used to create more dynamic rules at runtime, as long as you take care that any dynamic keys passed in don't conflict with your form data.
+
+### Protecting Fields
+
+To help protect against Mass Assignment Attacks, the Model class **requires** that you list all of the field names that can be changed during inserts, updates, and replacements in the [$allowedFields](#allowedfields) class property. Any data provided in addition to these will be removed prior to hitting the database. This limits which fields can be assigned through model methods.
+
+For `replace()`, a supplied [$primaryKey](#primarykey) is retained to identify the record to replace.
+
+```php
+protect(false)
+ ->insert($data)
+ ->protect(true);
+```
+
+### Runtime Return Type Changes
+
+You can specify the format that data should be returned as when using the **find\*()** methods as the class property, [$returnType](#returntype). There may be times that you would like the data back in a different format, though. The Model provides methods that allow you to do just that.
+
+!!! note "Note"
+ These methods only change the return type for the next **find\*()** method call. After that, it is reset to its default value.
+
+#### asArray()
+
+Returns data from the next **find\*()** method as associative arrays:
+
+```php
+asArray()->where('status', 'active')->findAll();
+```
+
+#### asObject()
+
+Returns data from the next **find\*()** method as standard objects or custom class instances:
+
+```php
+asObject()->where('status', 'active')->findAll();
+
+// Return as custom class instances
+$users = $userModel->asObject('User')->where('status', 'active')->findAll();
+```
+
+### Processing Large Amounts of Data
+
+Sometimes, you need to process large amounts of data and would run the risk of running out of memory. To make this simpler, you may use the `chunk()` method to get smaller chunks of data that you can then do your work on. The first parameter is the number of rows to retrieve in a single chunk. The second parameter is a Closure that will be called for each row of data.
+
+This is best used during cron jobs, data exports, or other large tasks.
+
+```php
+chunk(100, static function ($data) {
+ // do something.
+ // $data is a single row of data.
+});
+```
+
+## Working with Query Builder
+
+### Getting Query Builder for the Model's Table
+
+CodeIgniter Model has one instance of the Query Builder for that model's database connection. You can get access to the **shared** instance of the Query Builder any time you need it:
+
+```php
+builder();
+```
+
+This builder is already set up with the model's [$table](#table).
+
+!!! note "Note"
+ Once you get the Query Builder instance, you can call methods of the [Query Builder](../database/query_builder.md). However, since Query Builder is not a Model, you cannot call methods of the Model.
+
+### Getting Query Builder for Another Table
+
+If you need access to another table, you can get another instance of the Query Builder. Pass the table name in as a parameter, but be aware that this will **not** return a shared instance:
+
+```php
+builder('groups');
+```
+
+### Mixing Methods of Query Builder and Model
+
+You can also use Query Builder methods and the Model's CRUD methods in the same chained call, allowing for very elegant use:
+
+```php
+where('status', 'active')
+ ->orderBy('last_login', 'asc')
+ ->findAll();
+```
+
+In this case, it operates on the shared instance of the Query Builder held by the model.
+
+!!! important "Important"
+ The Model does not provide a perfect interface to the Query Builder. The Model and the Query Builder are separate classes with different purposes. They should not be expected to return the same data.
+
+If the Query Builder returns a result, it is returned as is. In that case, the result may be different from the one returned by the model's method and may not be what was expected. The model's events are not triggered.
+
+To prevent unexpected behavior, do not use Query Builder methods that return results and specify the model's method at the end of the method chaining.
+
+!!! note "Note"
+ You can also access the model's database connection seamlessly:
+
+ ```php
+ escape($name);
+ ```
+
+## Model Events
+
+There are several points within the model's execution at which you can specify multiple callback methods to run. These methods can be used to normalize data, hash passwords, save related entities, and much more.
+
+The following points in the model's execution can be affected, each through a class property:
+
+- [$beforeInsert](#beforeinsert), [$afterInsert](#afterinsert)
+- [$beforeUpdate](#beforeupdate), [$afterUpdate](#afterupdate)
+- [$beforeFind](#beforefind), [$afterFind](#afterfind)
+- [$beforeDelete](#beforedelete), [$afterDelete](#afterdelete)
+- [$beforeInsertBatch](#beforeinsertbatch), [$afterInsertBatch](#afterinsertbatch)
+- [$beforeUpdateBatch](#beforeupdatebatch), [$afterUpdateBatch](#afterupdatebatch)
+
+!!! note "Note"
+ `$beforeInsertBatch`, `$afterInsertBatch`, `$beforeUpdateBatch` and `$afterUpdateBatch` can be used since v4.3.0.
+
+### Defining Callbacks
+
+You specify the callbacks by first creating a new class method in your model to use.
+
+This class method will always receive a `$data` array as its only parameter.
+
+The exact contents of the `$data` array will vary between events, but will always contain a key named `data` that contains the primary data passed to the original method. In the case of the **insert\*()** or **update\*()** methods, that will be the key/value pairs that are being inserted into the database. The main `$data` array will also contain the other values passed to the method, as detailed in [Event Parameters](#event-parameters).
+
+The callback method must return the original `$data` array so other callbacks have the full information.
+
+```php
+allowCallbacks(false)->find(1); // No callbacks triggered
+$model->find(1); // Callbacks subject to original property value
+```
+
+### Event Parameters
+
+Since the exact data passed to each callback varies a bit, here are the details on what is in the `$data` parameter passed to each event:
+
+| Event | $data contents |
+|----|----|
+| beforeInsert | **data** = the key/value pairs that are being inserted. If an object or Entity class is passed to the `insert()` method, it is first converted to an array. |
+| afterInsert | **id** = the primary key of the new row, or 0 on failure. **data** = the key/value pairs being inserted. **result** = the results of the `insert()` method used through the Query Builder. |
+| beforeUpdate | **id** = the array of primary keys of the rows being passed to the `update()` method. **data** = the key/value pairs that are being updated. If an object or Entity class is passed to the `update()` method, it is first converted to an array. |
+| afterUpdate | **id** = the array of primary keys of the rows being passed to the `update()` method. **data** = the key/value pairs being updated. **result** = the results of the `update()` method used through the Query Builder. |
+| beforeFind | The name of the calling **method**, whether a **singleton** was requested, and these additional fields: none for `first()`, **id** = the primary key of the row being searched for, for `find()`, and **limit** = the number of rows to find, and **offset** = the number of rows to skip during the search, for `findAll()`. |
+| afterFind | Same as **beforeFind** but including the resulting row(s) of data, or null if no result found. |
+| beforeDelete | **id** = an array of primary key rows being passed to the `delete()` method. **purge** = boolean whether soft-deleted rows should be hard deleted. |
+| afterDelete | **id** = an array of primary key rows being passed to the `delete()` method. **purge** = boolean whether soft-deleted rows should be hard deleted. **result** = the result of the `delete()` call on the Query Builder. **data** = unused. |
+| beforeInsertBatch | **data** = associative array of values that are being inserted. If an object or Entity class is passed to the `insertBatch()` method, it is first converted to an array. |
+| afterInsertBatch | **data** = the associative array of values being inserted. **result** = the results of the `insertBatch()` method used through the Query Builder. |
+| beforeUpdateBatch | **data** = associative array of values that are being updated. If an object or Entity class is passed to the `updateBatch()` method, it is first converted to an array. |
+| afterUpdateBatch | **data** = the key/value pairs being updated. **result** = the results of the `updateBatch()` method used through the Query Builder. |
+
+!!! note "Note"
+ When using the `paginate()` method in combination with the `beforeFind` event to modify the query, the results may not behave as expected.
+
+ This is because the `beforeFind` event only affects the actual retrieval of the results (`findAll()`), but **not** the query used to count the total number of rows for pagination.
+
+ As a result, the total row count used for generating pagination links may not reflect the modified query conditions, leading to inconsistencies in pagination.
+
+### Modifying Find\* Data
+
+The `beforeFind` and `afterFind` methods can both return a modified set of data to override the normal response from the model. For `afterFind`, any changes made to `data` in the return array will automatically be passed back to the calling context. In order for `beforeFind` to intercept the find workflow, it must also return an additional boolean, `returnData`:
+
+```php
+getCachedItem($data['id'])) {
+ $data['data'] = $item;
+ $data['returnData'] = true;
+
+ return $data;
+ }
+
+ // ...
+ }
+}
+```
+
+## Manual Model Creation
+
+You do not need to extend any special class to create a model for your application. All you need is to get an instance of the database connection and you're good to go. This allows you to bypass the features CodeIgniter's Model gives you out of the box, and create a fully custom experience.
+
+```php
+db = $db;
+ }
+}
+```
diff --git a/docs/outgoing/alternative_php.md b/docs/outgoing/alternative_php.md
new file mode 100644
index 000000000000..38a3d18b808c
--- /dev/null
+++ b/docs/outgoing/alternative_php.md
@@ -0,0 +1,55 @@
+# Alternate PHP Syntax for View Files
+
+If you do not utilize a templating engine to simplify output, you'll be using pure PHP in your View files. To minimize the PHP code in these files, and to make it easier to identify the code blocks, it is recommended that you use PHP's alternative syntax for control structures and short tag echo statements. If you are not familiar with this syntax, it allows you to eliminate the braces from your code, and eliminate "echo" statements.
+
+## Alternative Echoes
+
+Normally, to echo or print out a variable, you would do this:
+
+```
+
+```
+
+With the alternative syntax, you can instead do it this way:
+
+```
+= esc($variable) ?>
+```
+
+## Alternative Control Structures
+
+Control structures, like if, for, foreach, and while can be written in a simplified format as well. Here is an example using `foreach`:
+
+```php
+
+
+
+
+ = esc($item) ?>
+
+
+
+
+```
+
+Notice that there are no braces. Instead, the end brace is replaced with `endforeach`. Each of the control structures listed above has a similar closing syntax: `endif`, `endfor`, `endforeach`, and `endwhile`.
+
+Also notice that instead of using a semicolon after each structure (except the last one), there is a colon. This is important!
+
+Here is another example, using `if`/`elseif`/`else`. Notice the colons:
+
+```php
+
+
+ Hi Sally
+
+
+
+ Hi Joe
+
+
+
+ Hi unknown user
+
+
+```
diff --git a/docs/outgoing/api_responses.md b/docs/outgoing/api_responses.md
new file mode 100644
index 000000000000..a6f802a62504
--- /dev/null
+++ b/docs/outgoing/api_responses.md
@@ -0,0 +1,520 @@
+# API Responses
+
+Much of modern PHP development requires building APIs, whether simply to provide data for a JavaScript-heavy single-page application, or as a standalone product. CodeIgniter provides a couple of traits that can be used with any controller to make common response types simple, with no need to remember which HTTP status code should be returned for which response types.
+
+## Response Examples
+
+The following example shows a common usage pattern within your controllers.
+
+```php
+save($this->request->getPost());
+
+ // Respond with 201 status code
+ return $this->respondCreated();
+ }
+}
+```
+
+In this example, an HTTP status code of 201 is returned, with the generic status message, 'Created'. Methods exist for the most common use cases:
+
+```php
+respond($data, 200);
+
+// Generic failure response
+$this->fail($errors, 400);
+
+// Item created response
+$this->respondCreated($data);
+
+// Item successfully deleted
+$this->respondDeleted($data);
+
+// Command executed by no response required
+$this->respondNoContent($message);
+
+// Client isn't authorized
+$this->failUnauthorized($description);
+
+// Forbidden action
+$this->failForbidden($description);
+
+// Resource Not Found
+$this->failNotFound($description);
+
+// Data was not validated
+$this->failValidationErrors($errors);
+
+// Resource already exists
+$this->failResourceExists($description);
+
+// Resource previously deleted
+$this->failResourceGone($description);
+
+// Client made too many requests
+$this->failTooManyRequests($description);
+```
+
+## Handling Response Types
+
+When you pass your data to any of these methods, they will determine the data type to format the results as based on the following criteria:
+
+- The format is determined according to the controller's `$this->format` value. If that is `null`, it will try to negotiate the content type with what the client asked for, defaulting to the first element (JSON by default) in the `$supportedResponseFormats` property within **app/Config/Format.php**.
+- The data will be formatted according to the format. If the format is not JSON and data is a string, it will be treated as HTML to send back to the client.
+
+!!! note "Note"
+ Prior to v4.5.0, due to a bug, if data is a string, it will be treated as HTML even if the format is JSON.
+
+To define the formatter that is used, edit **app/Config/Format.php**. The `$supportedResponseFormats` property contains a list of MIME types that your application can automatically format the response for. By default, the system knows how to format both XML and JSON responses:
+
+```php
+ \CodeIgniter\Format\JSONFormatter::class,
+ 'application/xml' => \CodeIgniter\Format\XMLFormatter::class,
+ ];
+
+ // ...
+}
+```
+
+So, if your request asks for JSON formatted data in an **Accept** header, the data array you pass to any of the `respond*` or `fail*` methods will be formatted by the `CodeIgniter\Format\JSONFormatter` class. The resulting JSON data will be sent back to the client.
+
+## Class Reference
+
+#### setResponseFormat($format)
+
+- **Parameters**
+ - **$format** `string` The type of response to return, either `json` or `xml`
+
+This defines the format to be used when formatting arrays in responses. If you provide a `null` value for `$format`, it will be automatically determined through content negotiation.
+
+```php
+setResponseFormat('json')->respond(['error' => false]);
+```
+
+#### respond($data[, $statusCode = 200[, $message = '']])
+
+- **Parameters**
+ - **$data** `mixed` The data to return to the client. Either string or array.
+ - **$statusCode** `int` The HTTP status code to return. Defaults to 200
+ - **$message** `string` A custom "reason" message to return.
+
+This is the method used by all other methods in this trait to return a response to the client.
+
+The `$data` element can be either a string or an array. By default, a string will be returned as HTML, while an array will be run through json_encode and returned as JSON, unless [Content Negotiation](../incoming/content_negotiation.md) determines it should be returned in a different format.
+
+If a `$message` string is passed, it will be used in place of the standard IANA reason codes for the response status. Not every client will respect the custom codes, though, and will use the IANA standards that match the status code.
+
+!!! note "Note"
+ Since it sets the status code and body on the active Response instance, this should always be the final method in the script execution.
+
+#### fail($messages[, int $status = 400[, string $code = null[, string $message = '']]])
+
+- **Parameters**
+ - **$messages** `mixed` A string or array of strings that contain error messages encountered.
+ - **$status** `int` The HTTP status code to return. Defaults to 400.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: A multi-part response in the client's preferred format.
+
+This is the generic method used to represent a failed response, and is used by all of the other "fail" methods.
+
+The `$messages` element can be either a string or an array of strings.
+
+The `$status` parameter is the HTTP status code that should be returned.
+
+Since many APIs are better served using custom error codes, a custom error code can be passed in the third parameter. If no value is present, it will be the same as `$status`.
+
+If a `$message` string is passed, it will be used in place of the standard IANA reason codes for the response status. Not every client will respect the custom codes, though, and will use the IANA standards that match the status code.
+
+The response is an array with three elements: `status`, `code`, and `messages`.
+
+- The `status` element contains the status code of the error.
+- The `code` element contains a custom, API-specific error code.
+- The `messages` element contains an array of error messages.
+
+Depending on the number of error messages, the response would look something like:
+
+```php
+ 400,
+ 'code' => '321',
+ 'messages' => [
+ 'error' => 'An error occurred',
+ ],
+];
+
+// Example response with multiple error messages per field
+$response = [
+ 'status' => 400,
+ 'code' => '321a',
+ 'messages' => [
+ 'foo' => 'Error message 1',
+ 'bar' => 'Error message 2',
+ ],
+];
+```
+
+#### respondCreated($data = null[, string $message = ''])
+
+- **Parameters**
+ - **$data** `mixed` The data to return to the client. Either string or array.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when a new resource was created, typically 201:
+
+```php
+insert($data);
+
+return $this->respondCreated($user);
+```
+
+#### respondDeleted($data = null[, string $message = ''])
+
+- **Parameters**
+ - **$data** `mixed` The data to return to the client. Either string or array.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when a resource was deleted as the result of this API call, typically 200.
+
+```php
+delete($id);
+
+return $this->respondDeleted(['id' => $id]);
+```
+
+#### respondNoContent(string $message = 'No Content')
+
+- **Parameters**
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when a command was successfully executed by the server but there is no meaningful reply to send back to the client, typically 204.
+
+```php
+respondNoContent();
+```
+
+#### failUnauthorized(string $description = 'Unauthorized'[, string $code = null[, string $message = '']])
+
+- **Parameters**
+ - **$description** `string` The error message to show the user.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when the user either has not been authorized, or has incorrect authorization. Status code is 401.
+
+```php
+failUnauthorized('Invalid Auth token');
+```
+
+#### failForbidden(string $description = 'Forbidden'[, string $code=null[, string $message = '']])
+
+- **Parameters**
+ - **$description** `string` The error message to show the user.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Unlike `failUnauthorized()`, this method should be used when the requested API endpoint is never allowed. Unauthorized implies the client is encouraged to try again with different credentials. Forbidden means the client should not try again because it won't help. Status code is 403.
+
+```php
+failForbidden('Invalid API endpoint.');
+```
+
+#### failNotFound(string $description = 'Not Found'[, string $code=null[, string $message = '']])
+
+- **Parameters**
+ - **$description** `string` The error message to show the user.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when the requested resource cannot be found. Status code is 404.
+
+```php
+failNotFound('User 13 cannot be found.');
+```
+
+#### failValidationErrors($errors[, string $code=null[, string $message = '']])
+
+- **Parameters**
+ - **$errors** `mixed` The error message or array of messages to show the user.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when data the client sent did not pass validation rules. Status code is typically 400.
+
+```php
+failValidationErrors($validation->getErrors());
+```
+
+#### failResourceExists(string $description = 'Conflict'[, string $code=null[, string $message = '']])
+
+- **Parameters**
+ - **$description** `string` The error message to show the user.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when the resource the client is trying to create already exists. Status code is typically 409.
+
+```php
+failResourceExists('A user already exists with that email.');
+```
+
+#### failResourceGone(string $description = 'Gone'[, string $code=null[, string $message = '']])
+
+- **Parameters**
+ - **$description** `string` The error message to show the user.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when the requested resource was previously deleted and is no longer available. Status code is typically 410.
+
+```php
+failResourceGone('That user has been previously deleted.');
+```
+
+#### failTooManyRequests(string $description = 'Too Many Requests'[, string $code=null[, string $message = '']])
+
+- **Parameters**
+ - **$description** `string` The error message to show the user.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when the client has called an API endpoint too many times. This might be due to some form of throttling or rate limiting. Status code is typically 429.
+
+```php
+failTooManyRequests('You must wait 15 seconds before making another request.');
+```
+
+#### failServerError(string $description = 'Internal Server Error'[, string $code = null[, string $message = '']])
+
+- **Parameters**
+ - **$description** `string` The error message to show the user.
+ - **$code** `string` A custom, API-specific, error code.
+ - **$message** `string` A custom "reason" message to return.
+
+- **Returns**: The value of the Response object's `send()` method.
+
+Sets the appropriate status code to use when there is a server error.
+
+```php
+failServerError('Server error.');
+```
+
+## Pagination Responses
+
+When returning paginated results from an API endpoint, you can use the `paginate()` method to return the results along with the pagination information. This helps to keep consistent responses across your API, while providing all of the information that clients will need to properly page through the results.
+
+### Example Usage
+
+```php
+where('active', 1);
+
+ return $this->paginate($model, 20);
+ }
+}
+```
+
+A typical response might look like:
+
+```json
+{
+ "data": [
+ {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@example.com"
+ },
+ {
+ "id": 2,
+ "username": "user",
+ "email": "user@example.com"
+ }
+ ],
+ "meta": {
+ "page": 1,
+ "perPage": 20,
+ "total": 2,
+ "totalPages": 1
+ },
+ "links": {
+ "self": "http://example.com/users?page=1",
+ "first": "http://example.com/users?page=1",
+ "last": "http://example.com/users?page=1",
+ "next": null,
+ "prev": null
+ }
+}
+```
+
+The `paginate()` method will always wrap the results in a `data` element, and will also include `meta` and `links` elements to help the client page through the results. If there are no results, the `data` element will be an empty array, and the `meta` and `links` elements will still be present, but with values that indicate no results.
+
+You can also pass it a Builder instance instead of a Model, as long as the Builder is properly configured with the table name and any necessary joins or where clauses.
+
+```php
+table('users')
+ ->where('active', 1);
+
+ return $this->paginate(resource: $builder, perPage: 20);
+ }
+}
+```
+
+## Class Reference
+
+#### paginate(Model|BaseBuilder $resource, int $perPage = 20, ?string $transformWith = null)
+
+- **Parameters**
+ - **$resource** `Model|BaseBuilder` The resource to paginate, either a Model or a Builder instance.
+ - **$perPage** `int` The number of items to return per page.
+ - **$transformWith** `string|null` Optional transformer class name to transform the results.
+
+Generates a paginated response from the given resource. The resource can be either a Model or a Builder instance. The method will automatically determine the current page from the request's query parameters. The response will include the paginated data, along with metadata about the pagination state and links to navigate through the pages.
+
+If you provide a `$transformWith` parameter with a transformer class name, each item in the paginated results will be transformed using that transformer before being returned. This is useful for controlling the structure and content of your API responses. See [API Transformers](api_transformers.md#api-resources) for more information on creating and using transformers.
+
+Example with transformer:
+
+```php
+paginate(resource: $model, perPage: 20, transformWith: UserTransformer::class);
+ }
+}
+```
diff --git a/docs/outgoing/api_transformers.md b/docs/outgoing/api_transformers.md
new file mode 100644
index 000000000000..59dcf75717df
--- /dev/null
+++ b/docs/outgoing/api_transformers.md
@@ -0,0 +1,744 @@
+# API Resources
+
+When building APIs, you often need to transform your data models into a consistent format before sending them to the client. API Resources, implemented through transformers, provide a clean way to convert your entities, arrays, or objects into structured API responses. They help separate your internal data structure from what you expose through your API, making it easier to maintain and evolve your API over time.
+
+## Quick Example
+
+The following example shows a common usage pattern for transformers in your application.
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'email' => $resource['email'],
+ 'created_at' => $resource['created_at'],
+ ];
+ }
+}
+
+// In your controller
+$user = model('UserModel')->find(1);
+$transformer = new UserTransformer();
+
+return $this->respond($transformer->transform($user));
+```
+
+In this example, the `UserTransformer` defines which fields from a user entity should be included in the API response. The `transform()` method converts a single resource, while `transformMany()` handles collections of resources.
+
+## Creating a Transformer
+
+To create a transformer, extend the `BaseTransformer` class and implement the `toArray()` method to define your API resource structure. The `toArray()` method receives the resource being transformed as a parameter, allowing you to access and transform its data.
+
+### Basic Transformer
+
+```php
+ $resource['id'],
+ 'username' => $resource['name'], // Renaming the field
+ 'email' => $resource['email'],
+ 'member_since' => date('Y-m-d', strtotime($resource['created_at'])), // Formatting
+ ];
+ }
+}
+```
+
+The `toArray()` method receives the resource (entity, array, or object) as its parameter and defines the structure of your API response. You can include any fields you want from the resource, and you can also rename or transform values as needed.
+
+### Generating Transformer Files
+
+CodeIgniter provides a CLI command to quickly generate transformer skeleton files:
+
+```console
+php spark make:transformer User
+```
+
+This creates a new transformer file at **app/Transformers/User.php** with the basic structure already in place.
+
+#### Command Options
+
+The `make:transformer` command supports several options:
+
+**--suffix**
+Appends "Transformer" to the class name:
+
+```console
+php spark make:transformer User --suffix
+```
+
+Creates **app/Transformers/UserTransformer.php**
+
+**--namespace**
+Specifies a custom root namespace:
+
+```console
+php spark make:transformer User --namespace MyCompany\API
+```
+
+**--force**
+Forces overwriting an existing file:
+
+```console
+php spark make:transformer User --force
+```
+
+#### Subdirectories
+
+You can organize transformers into subdirectories by including the path in the name:
+
+```console
+php spark make:transformer api/v1/User
+```
+
+This creates **app/Transformers/Api/V1/User.php** with the appropriate namespace `App\Transformers\Api\V1`.
+
+### Using Transformers in Controllers
+
+Once you've created a transformer, you can use it in your controllers to transform data before returning it to the client.
+
+```php
+find($id);
+
+ if (! $user) {
+ return $this->failNotFound('User not found');
+ }
+
+ $transformer = new UserTransformer();
+
+ return $this->respond($transformer->transform($user));
+ }
+
+ public function index()
+ {
+ $users = model('UserModel')->findAll();
+
+ $transformer = new UserTransformer();
+
+ return $this->respond($transformer->transformMany($users));
+ }
+}
+```
+
+## Field Filtering
+
+The transformer automatically supports field filtering through the `fields` query parameter of the current URL. This allows API clients to request only the specific fields they need, reducing bandwidth and improving performance.
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'email' => $resource['email'],
+ 'created_at' => $resource['created_at'],
+ 'updated_at' => $resource['updated_at'],
+ ];
+ }
+}
+
+// Request: GET /users/1?fields=id,name
+// Response: {"id": 1, "name": "John Doe"}
+```
+
+A request to `/users/1?fields=id,name` would return only:
+
+```json
+{
+ "id": 1,
+ "name": "John Doe"
+}
+```
+
+### Restricting Available Fields
+
+By default, clients can request any field defined in your `toArray()` method. You can restrict which fields are allowed by overriding the `getAllowedFields()` method:
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'email' => $resource['email'],
+ 'created_at' => $resource['created_at'],
+ 'updated_at' => $resource['updated_at'],
+ ];
+ }
+
+ protected function getAllowedFields(): ?array
+ {
+ // Only these fields can be requested
+ return ['id', 'name', 'created_at'];
+ }
+}
+```
+
+Now, even if a client requests `/users/1?fields=email`, an `ApiException` will be thrown because `email` is not in the allowed fields list.
+
+## Including Related Resources
+
+Transformers support loading related resources through the `include` query parameter. This follows a common API pattern where clients can specify which relationships they want included. While relationships are the most frequent use case, you can include any additional data you want by defining custom include methods.
+
+### Defining Include Methods
+
+To support including related resources, create methods prefixed with `include` followed by the resource name. Inside these methods, you can access the current resource being transformed via `$this->resource`:
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'email' => $resource['email'],
+ ];
+ }
+
+ protected function includePosts(): array
+ {
+ // Use $this->resource to access the current resource being transformed
+ $posts = model('PostModel')->where('user_id', $this->resource['id'])->findAll();
+
+ return (new PostTransformer())->transformMany($posts);
+ }
+
+ protected function includeComments(): array
+ {
+ $comments = model('CommentModel')->where('user_id', $this->resource['id'])->findAll();
+
+ return (new CommentTransformer())->transformMany($comments);
+ }
+}
+```
+
+Note how the include methods use `$this->resource['id']` to access the ID of the user being transformed. The `$this->resource` property is automatically set by the transformer when `transform()` is called.
+
+Clients can now request: `/users/1?include=posts,comments`
+
+The response would include:
+
+```json
+{
+ "id": 1,
+ "name": "John Doe",
+ "email": "john@example.com",
+ "posts": [
+ {
+ "id": 1,
+ "title": "First Post"
+ }
+ ],
+ "comments": [
+ {
+ "id": 1,
+ "content": "Great article!"
+ }
+ ]
+}
+```
+
+!!! note "Note"
+ The `fields` and `include` query parameters describe the **root** resource only. Transformers instantiated inside an `include*()` method (such as `PostTransformer` above) are treated as nested resources: when created **without an explicit request** they do **not** inherit the root request's `fields`/`include` state. This prevents the parent's query parameters from leaking into related resources, which could otherwise cause incorrect field filtering or unexpected/recursive includes. If you deliberately pass a request to a nested transformer, that request's scope is honored.
+
+### Restricting Available Includes
+
+Similar to field filtering, you can restrict which relationships can be included by overriding the `getAllowedIncludes()` method:
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'email' => $resource['email'],
+ ];
+ }
+
+ protected function getAllowedIncludes(): ?array
+ {
+ // Only these relationships can be included
+ return ['posts', 'comments'];
+ }
+
+ protected function includePosts(): array
+ {
+ $posts = model('PostModel')->where('user_id', $this->resource['id'])->findAll();
+
+ return (new PostTransformer())->transformMany($posts);
+ }
+
+ protected function includeComments(): array
+ {
+ $comments = model('CommentModel')->where('user_id', $this->resource['id'])->findAll();
+
+ return (new CommentTransformer())->transformMany($comments);
+ }
+
+ protected function includeOrders(): array
+ {
+ // This method exists but won't be callable from the API
+ // because 'orders' is not in getAllowedIncludes()
+ $orders = model('OrderModel')->where('user_id', $this->resource['id'])->findAll();
+
+ return (new OrderTransformer())->transformMany($orders);
+ }
+}
+```
+
+If you want to disable all includes, return an empty array:
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'email' => $resource['email'],
+ ];
+ }
+
+ protected function getAllowedIncludes(): ?array
+ {
+ // Return empty array to disable all includes
+ return [];
+ }
+}
+```
+
+### Include Validation
+
+The transformer automatically validates that any requested includes have corresponding `include*()` methods defined in your transformer class. If a client requests an include that doesn't exist, an `ApiException` will be thrown.
+
+For example, if a client requests:
+
+```
+GET /api/users?include=invalid
+```
+
+And your transformer doesn't have an `includeInvalid()` method, an exception will be thrown with the message: "Missing include method for: invalid".
+
+This helps catch typos and prevents unexpected behavior.
+
+## Transforming Collections
+
+The `transformMany()` method makes it easy to transform arrays of resources:
+
+```php
+findAll();
+
+ $transformer = new UserTransformer();
+ $data = $transformer->transformMany($users);
+
+ return $this->respond($data);
+ }
+}
+```
+
+The `transformMany()` method applies the same transformation logic to each item in the collection, including any field filtering or includes specified in the request.
+
+## Working with Different Data Types
+
+Transformers can handle various data types, not just entities.
+
+### Transforming Entities
+
+When you pass an `Entity` instance to `transform()`, it automatically calls the entity's `toArray()` method to get the data:
+
+```php
+ 1,
+ 'name' => 'John Doe',
+ 'email' => 'john@example.com',
+]);
+
+$transformer = new UserTransformer();
+$result = $transformer->transform($user);
+```
+
+### Transforming Arrays
+
+You can transform plain arrays as well:
+
+```php
+ 1,
+ 'name' => 'John Doe',
+ 'email' => 'john@example.com',
+];
+
+$transformer = new UserTransformer();
+$result = $transformer->transform($userData);
+```
+
+### Transforming Objects
+
+Any object can be cast to an array and transformed:
+
+```php
+id = 1;
+$user->name = 'John Doe';
+$user->email = 'john@example.com';
+
+$transformer = new UserTransformer();
+$result = $transformer->transform($user);
+```
+
+### Using toArray() Only
+
+If you don't pass a resource to `transform()`, it will use the data from your `toArray()` method:
+
+```php
+ '1.0',
+ 'status' => 'active',
+ 'message' => 'API is running',
+ ];
+ }
+}
+
+// Usage
+$transformer = new StaticDataTransformer();
+$result = $transformer->transform(null); // No resource passed
+```
+
+## Class Reference
+
+## CodeIgniter\API
+
+### BaseTransformer
+
+#### \__construct(?IncomingRequest $request = null)
+
+- **Parameters**
+ - **$request** `IncomingRequest|null` Optional request instance. If not provided, the global request will be used.
+
+Initializes the transformer and extracts the `fields` and `include` query parameters from the request.
+
+!!! note "Note"
+ When no request is explicitly passed, the global request's `fields` and `include` are read **only for the root transformer**. A transformer constructed during a nested transformation (i.e. inside an `include*()` method) without an explicit request still uses the global request object, but does not read `fields`/`include` from it, to avoid leaking the root's scope. Passing an explicit request always applies that request's scope, regardless of nesting.
+
+#### toArray(mixed $resource)
+
+- **Parameters**
+ - **$resource** `mixed` The resource being transformed (Entity, array, object, or null)
+
+- **Returns**: The array representation of the resource
+
+- **Return type**: `array`
+
+This abstract method must be implemented by child classes to define the structure of the API resource. The resource parameter contains the data being transformed. Return an array with the fields you want to include in the API response, accessing data from the `$resource` parameter.
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'price' => $resource['price'],
+ 'in_stock' => $resource['stock_quantity'] > 0,
+ 'description' => $resource['description'],
+ ];
+ }
+}
+```
+
+#### transform($resource = null)
+
+- **Parameters**
+ - **$resource** `mixed` The resource to transform (Entity, array, object, or null)
+
+- **Returns**: The transformed array
+
+- **Return type**: `array`
+
+Transforms the given resource into an array by calling `toArray()` with the resource data. If `$resource` is `null`, passes `null` to `toArray()`. If it's an Entity, extracts its array representation first. Otherwise, casts it to an array.
+
+The resource is also stored in `$this->resource` so include methods can access it.
+
+The method automatically applies field filtering and includes based on query parameters.
+
+```php
+find(1);
+
+$transformer = new UserTransformer();
+
+// Transform an entity
+$result = $transformer->transform($user);
+
+// Transform an array
+$userData = ['id' => 1, 'name' => 'John Doe'];
+$result = $transformer->transform($userData);
+
+// Use toArray() data
+$result = $transformer->transform();
+```
+
+#### transformMany(array $resources)
+
+- **Parameters**
+ - **$resources** `array` The array of resources to transform
+
+- **Returns**: Array of transformed resources
+
+- **Return type**: `array`
+
+Transforms a collection of resources by calling `transform()` on each item. Field filtering and includes are applied consistently to all items.
+
+```php
+findAll();
+
+$transformer = new UserTransformer();
+$results = $transformer->transformMany($users);
+
+// $results is an array of transformed user arrays
+foreach ($results as $user) {
+ // Each $user is the result of calling transform() on an individual user
+}
+```
+
+#### getAllowedFields()
+
+- **Returns**: Array of allowed field names, or `null` to allow all fields
+- **Return type**: `array|null`
+
+Override this method to restrict which fields can be requested via the `fields` query parameter. Return `null` (the default) to allow all fields from `toArray()`. Return an array of field names to create a whitelist of allowed fields.
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'email' => $resource['email'],
+ 'created_at' => $resource['created_at'],
+ ];
+ }
+
+ protected function getAllowedFields(): ?array
+ {
+ // Clients can only request id, name, and created_at
+ // Attempting to request 'email' will throw an ApiException
+ return ['id', 'name', 'created_at'];
+ }
+}
+```
+
+#### getAllowedIncludes()
+
+- **Returns**: Array of allowed include names, or `null` to allow all includes
+- **Return type**: `array|null`
+
+Override this method to restrict which related resources can be included via the `include` query parameter. Return `null` (the default) to allow all includes that have corresponding methods. Return an array of include names to create a whitelist. Return an empty array to disable all includes.
+
+```php
+ $resource['id'],
+ 'name' => $resource['name'],
+ 'email' => $resource['email'],
+ ];
+ }
+
+ protected function getAllowedIncludes(): ?array
+ {
+ // Only 'posts' can be included via ?include=posts
+ // Attempting to include 'orders' will throw an ApiException
+ return ['posts'];
+ }
+
+ protected function includePosts(): array
+ {
+ $posts = model('PostModel')->where('user_id', $this->resource['id'])->findAll();
+
+ return (new PostTransformer())->transformMany($posts);
+ }
+
+ protected function includeOrders(): array
+ {
+ // This method exists but cannot be called via the API
+ $orders = model('OrderModel')->where('user_id', $this->resource['id'])->findAll();
+
+ return (new OrderTransformer())->transformMany($orders);
+ }
+}
+```
+
+## Exception Reference
+
+### ApiException
+
+#### forInvalidFields(string $field)
+
+- **Parameters**
+ - **$field** `string` The invalid field name(s)
+
+- **Returns**: ApiException instance
+
+- **Return type**: `ApiException`
+
+Thrown when a client requests a field via the `fields` query parameter that is not in the allowed fields list.
+
+#### forInvalidIncludes(string $include)
+
+- **Parameters**
+ - **$include** `string` The invalid include name(s)
+
+- **Returns**: ApiException instance
+
+- **Return type**: `ApiException`
+
+Thrown when a client requests an include via the `include` query parameter that is not in the allowed includes list.
+
+#### forMissingInclude(string $include)
+
+- **Parameters**
+ - **$include** `string` The missing include method name
+
+- **Returns**: ApiException instance
+
+- **Return type**: `ApiException`
+
+Thrown when a client requests an include via the `include` query parameter, but the corresponding `include*()` method does not exist in the transformer class. This validation ensures that all requested includes have proper handler methods defined.
diff --git a/docs/outgoing/csp.md b/docs/outgoing/csp.md
new file mode 100644
index 000000000000..42dbd14bc021
--- /dev/null
+++ b/docs/outgoing/csp.md
@@ -0,0 +1,202 @@
+# Content Security Policy
+
+## What is Content Security Policy?
+
+One of the best protections you have against XSS attacks is to implement a Content Security Policy (CSP) on the site. This requires you to specify and authorize each source of content that is included in your site's HTML, including images, stylesheets, JavaScript files, and so on. The browser will reject content from sources that are not explicitly approved. This authorization is defined within the response's `Content-Security-Policy` header and offers various configuration options.
+
+This sounds complex, and on some sites can definitely be challenging. For many simple sites, though, where all content is served by the same domain (e.g., **http://example.com**), it is very simple to integrate.
+
+As this is a complex subject, this user guide will not go over all of the details. For more information, you should visit the following sites:
+
+- [Content Security Policy main site](https://content-security-policy.com/)
+- [W3C Specification](https://www.w3.org/TR/CSP)
+- [Introduction at HTML5Rocks](https://www.html5rocks.com/en/tutorials/security/content-security-policy/)
+- [Article at SitePoint](https://www.sitepoint.com/improving-web-security-with-the-content-security-policy/)
+
+## Turning CSP On
+
+!!! important "Important"
+ The [Debug Toolbar](../testing/debugging.md#the-debug-toolbar) may use Kint, which outputs inline scripts. Therefore, when CSP is turned on, a CSP nonce is automatically output for the Debug Toolbar. However, if you are not using CSP nonces, this will change the CSP header to something you do not intend, and it will behave differently than in production; if you want to verify CSP behavior, turn off the Debug Toolbar.
+
+By default, support for this is off. To enable support in your application, edit the `CSPEnabled` value in **app/Config/App.php**:
+
+```php
+response->getCSP()` in your controllers.
+
+The class holds a number of methods that map pretty clearly to the appropriate header value that you need to set. Examples are shown below, with different combinations of parameters, though all accept either a directive name or an array of them:
+
+```php
+response->getCSP();
+
+// specify the default directive treatment
+$csp->reportOnly(false);
+
+// specify the origin to use if none provided for a directive
+$csp->setDefaultSrc('cdn.example.com');
+
+// specify the URL that "report-only" reports get sent to
+$csp->setReportURI('http://example.com/csp/reports');
+
+// specify that HTTP requests be upgraded to HTTPS
+$csp->upgradeInsecureRequests(true);
+
+// add types or origins to CSP directives
+// assuming that the default treatment is to block rather than just report
+$csp->addBaseURI('example.com', true); // report only
+$csp->addChildSrc('https://youtube.com'); // blocked
+$csp->addConnectSrc('https://*.facebook.com', false); // blocked
+$csp->addFontSrc('fonts.example.com');
+$csp->addFormAction('self');
+$csp->addFrameAncestor('none', true); // report this one
+$csp->addImageSrc('cdn.example.com');
+$csp->addMediaSrc('cdn.example.com');
+$csp->addManifestSrc('cdn.example.com');
+$csp->addObjectSrc('cdn.example.com', false); // reject from here
+$csp->addPluginType('application/pdf', false); // reject this media type
+$csp->addScriptSrc('scripts.example.com', true); // allow but report requests from here
+$csp->addStyleSrc('css.example.com');
+$csp->addSandbox(['allow-forms', 'allow-scripts']);
+
+// the following CSP3 directives are available in v4.7.0 and later
+$csp->addScriptSrcAttr('trusted.com');
+$csp->addScriptSrcElem('trusted.com');
+$csp->addStyleSrcAttr('trusted.com');
+$csp->addStyleSrcElem('trusted.com');
+$csp->addWorkerSrc('workers.example.com');
+```
+
+The first parameter to each of the "add" methods is an appropriate string value, or an array of them.
+
+### Report Only
+
+The `reportOnly()` method allows you to specify the default reporting treatment for subsequent sources, unless overridden.
+
+For instance, you could specify that youtube.com was allowed, and then provide several allowed but reported sources:
+
+```php
+response->getCSP();
+
+$csp->addChildSrc('https://youtube.com'); // allowed
+$csp->reportOnly(true);
+$csp->addChildSrc('https://metube.com'); // allowed but reported
+$csp->addChildSrc('https://ourtube.com', false); // allowed
+```
+
+### Reporting Directives
+
+To specify the URL you want the reports to be sent to, you can use the `setReportURI()` method.
+
+!!! success "Available from version 4.7.0"
+
+CSP Level 3 deprecates the `report-uri` directive in favor of `report-to`. Therefore, you can use the `setReportToEndpoint()` method to set the reporting endpoint for CSP reports. Before adding this directive, make sure the reporting endpoints are already defined using the `addReportingEndpoints()` method.
+
+```php
+response->getCSP();
+
+$csp->setReportURI('https://example.com/csp-reports');
+
+// Starting in v4.7.0, you can use the setReportToEndpoint() method
+// to set the reporting endpoint for CSP reports
+$csp->addReportingEndpoints([
+ 'default' => 'https://example.com/csp-reports',
+ 'reports' => 'https://example.com/other-csp-reports',
+]);
+$csp->setReportToEndpoint('default');
+```
+
+For backward compatibility with browsers that do not support the `report-to` directive, CodeIgniter4 will also set the `report-uri` directive when you use the `setReportToEndpoint()` method.
+
+### Clear Directives
+
+If you want to clear existing CSP directives, you can use the `clearDirective()` method:
+
+```php
+response->getCSP();
+
+$csp->clearDirective('style-src');
+```
+
+## Inline Content
+
+Inline scripts and styles can be injected through user-generated content. To allow only trusted inline content, CSP lets you add a nonce to trusted `
+```
+
+!!! warning "Warning"
+ If an attacker injects a string like `
+
+// Becomes
+
+
+// OR
+
+```
diff --git a/docs/outgoing/index.md b/docs/outgoing/index.md
new file mode 100644
index 000000000000..685d921b5562
--- /dev/null
+++ b/docs/outgoing/index.md
@@ -0,0 +1,17 @@
+# Building Responses
+
+View components are used to build what is returned to the user.
+
+- [Views](views.md)
+- [View Renderer](view_renderer.md)
+- [View Layouts](view_layouts.md)
+- [View Cells](view_cells.md)
+- [View Parser](view_parser.md)
+- [View Decorators](view_decorators.md)
+- [HTML Table Class](table.md)
+- [HTTP Responses](response.md)
+- [API Responses](api_responses.md)
+- [API Resources](api_transformers.md)
+- [Content Security Policy](csp.md)
+- [Localization](localization.md)
+- [Alternate PHP Syntax for View Files](alternative_php.md)
diff --git a/docs/outgoing/localization.md b/docs/outgoing/localization.md
new file mode 100644
index 000000000000..e1380582e2b5
--- /dev/null
+++ b/docs/outgoing/localization.md
@@ -0,0 +1,515 @@
+# Localization
+
+## Working with Locales
+
+CodeIgniter provides several tools to help you localize your application for different languages. While full localization of an application is a complex subject, it's simple to swap out strings in your application with different supported languages.
+
+### Configuring the Locale
+
+#### Setting the Default Locale
+
+Every site will have a default language/locale it operates in. This can be set in **app/Config/App.php**:
+
+```php
+get('{locale}/books', 'App\Books::index');
+```
+
+In this example, if the user tried to visit **http://example.com/fr/books**, then the locale would be set to `fr`, assuming it was configured as a valid locale.
+
+If the value doesn't match a valid locale as defined in `$supportedLocales` in **app/Config/App.php**, the default locale will be used in its place, unless you choose to use only the supported locales defined in the App configuration file:
+
+```php
+$routes->useSupportedLocalesOnly(true);
+```
+
+!!! note "Note"
+ The `useSupportedLocalesOnly()` method can be used since v4.3.0.
+
+### Setting the Current Locale
+
+#### IncomingRequest Locale
+
+If you want to set the locale directly, you may use the `setLocale()` method in the [IncomingRequest Class](../incoming/incomingrequest.md):
+
+```php
+/** @var \CodeIgniter\HTTP\IncomingRequest $request */
+$request->setLocale('ja');
+```
+
+Before setting the locale, you must set valid locales, because any attempt to set a locale that is not valid will result in the [default locale](#setting-the-default-locale) being set.
+
+By default, the valid locales are defined in `Config\App::$supportedLocales` in **app/Config/App.php**:
+
+```php
+setLocale('ja');
+```
+
+### Retrieving the Current Locale
+
+The current locale can always be retrieved from the IncomingRequest object, through the `getLocale()` method. If your controller is extending `CodeIgniter\Controller`, this will be available through `$this->request`:
+
+```php
+request->getLocale();
+ }
+}
+```
+
+Alternatively, you can use the [Services class](../concepts/services.md) to retrieve the current request:
+
+```php
+$locale = service('request')->getLocale();
+```
+
+## Language Localization
+
+### Creating Language Files
+
+Language strings are stored in the **app/Language** directory, with a sub-directory for each supported language (locale):
+
+```
+app/
+ Language/
+ en/
+ App.php
+ fr/
+ App.php
+```
+
+!!! note "Note"
+ The Language Files do not have namespaces.
+
+Languages do not have any specific naming convention that is required. The file should be named logically to describe the type of content it holds. For example, let's say you want to create a file containing error messages. You might name it simply: **Errors.php**.
+
+Within the file, you would return an array, where each element in the array has a language key and can have a string to return:
+
+```php
+ 'The actual message to be shown.',
+];
+```
+
+!!! note "Note"
+ You cannot use dots (`.`) at the beginning and end of language keys.
+
+It also supports nested definitions:
+
+```php
+ [
+ 'nested' => [
+ 'key' => 'The actual message to be shown.',
+ ],
+ ],
+];
+```
+
+```php
+ 'You must submit an email address',
+ 'errorURLMissing' => 'You must submit a URL',
+ 'errorUsernameMissing' => 'You must submit a username',
+ 'nested' => [
+ 'error' => [
+ 'message' => 'A specific error message',
+ ],
+ ],
+];
+```
+
+### Basic Usage
+
+You can use the `lang()` helper function to retrieve text from any of the language files, by passing the filename and the language key as the first parameter, separated by a period (`.`).
+
+For example, to load the `errorEmailMissing` string from the **Errors.php** language file, you would do the following:
+
+```php
+echo lang('Errors.errorEmailMissing');
+```
+
+For nested definitions, you would do the following:
+
+```php
+echo lang('Errors.nested.error.message');
+```
+
+If the requested language key doesn't exist in the file for the current locale (after [Language Fallback](#language-fallback)), the string will be passed back, unchanged. In this example, it would return `Errors.errorEmailMissing` or `Errors.nested.error.message` if it didn't exist.
+
+#### Replacing Parameters
+
+!!! note "Note"
+ The following functions all require the [intl](https://www.php.net/manual/en/book.intl.php) extension to be loaded on your system in order to work. If the extension is not loaded, no replacement will be attempted. A great overview can be found over at [Sitepoint](https://www.sitepoint.com/localization-demystified-understanding-php-intl/).
+
+You can pass an array of values to replace placeholders in the language string as the second parameter to the `lang()` function. This allows for very simple number translations and formatting:
+
+```php
+ 'I have {0, number} apples.',
+ 'men' => 'The top {1, number} men out-performed the remaining {0, number}',
+ 'namedApples' => 'I have {number_apples, number, integer} apples.',
+];
+
+// Displays "I have 3 apples."
+echo lang('Tests.apples', [3]);
+```
+
+The first item in the placeholder corresponds to the index of the item in the array, if it's numerical:
+
+```php
+// Displays "The top 23 men out-performed the remaining 20"
+echo lang('Tests.men', [20, 23]);
+```
+
+You can also use named keys to make it easier to keep things straight, if you'd like:
+
+```php
+// Displays "I have 3 apples."
+echo lang('Tests.namedApples', ['number_apples' => 3]);
+```
+
+Obviously, you can do more than just number replacement. According to the [official ICU docs](https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classMessageFormat.html#details) for the underlying library, the following types of data can be replaced:
+
+- numbers - integer, currency, percent
+- dates - short, medium, long, full
+- time - short, medium, long, full
+- spellout - spells out numbers (i.e., 34 becomes thirty-four)
+- ordinal
+- duration
+
+Here are a few examples:
+
+```php
+ 'The time is now {0, time, short}.',
+ 'mediumTime' => 'The time is now {0, time, medium}.',
+ 'longTime' => 'The time is now {0, time, long}.',
+ 'fullTime' => 'The time is now {0, time, full}.',
+ 'shortDate' => 'The date is now {0, date, short}.',
+ 'mediumDate' => 'The date is now {0, date, medium}.',
+ 'longDate' => 'The date is now {0, date, long}.',
+ 'fullDate' => 'The date is now {0, date, full}.',
+ 'spelledOut' => '34 is {0, spellout}',
+ 'ordinal' => 'The ordinal is {0, ordinal}',
+ 'duration' => 'It has been {0, duration}',
+];
+
+// Displays "The time is now 11:18 PM"
+echo lang('Tests.shortTime', [time()]);
+// Displays "The time is now 11:18:50 PM"
+echo lang('Tests.mediumTime', [time()]);
+// Displays "The time is now 11:19:09 PM CDT"
+echo lang('Tests.longTime', [time()]);
+// Displays "The time is now 11:19:26 PM Central Daylight Time"
+echo lang('Tests.fullTime', [time()]);
+
+// Displays "The date is now 8/14/16"
+echo lang('Tests.shortDate', [time()]);
+// Displays "The date is now Aug 14, 2016"
+echo lang('Tests.mediumDate', [time()]);
+// Displays "The date is now August 14, 2016"
+echo lang('Tests.longDate', [time()]);
+// Displays "The date is now Sunday, August 14, 2016"
+echo lang('Tests.fullDate', [time()]);
+
+// Displays "34 is thirty-four"
+echo lang('Tests.spelledOut', [34]);
+
+// Displays "It has been 408,676:24:35"
+echo lang('Tests.ordinal', [time()]);
+```
+
+You should be sure to read up on the MessageFormatter class and the underlying ICU formatting to get a better idea of what capabilities it has, like performing conditional replacement, pluralization, and more. Both of the links provided earlier will give you an excellent idea as to the options available.
+
+#### Specifying Locale
+
+To specify a different locale to be used when replacing parameters, you can pass the locale in as the third parameter to the `lang()` function.
+
+```php
+ 7.41], 'en-GB');
+// Displays "$7.41"
+echo lang('{price, number, currency}', ['price' => 7.41], 'en-US');
+```
+
+If you want to change the current locale, see [Language Locale](#language-locale).
+
+#### Nested Arrays
+
+Language files also allow nested arrays to make working with lists, etc. easier.
+
+```php
+ [
+ 'Apples',
+ 'Bananas',
+ 'Grapes',
+ 'Lemons',
+ 'Oranges',
+ 'Strawberries',
+ ],
+];
+
+// Displays "Apples, Bananas, Grapes, Lemons, Oranges, Strawberries"
+echo implode(', ', lang('Fruit.list'));
+```
+
+### Language Fallback
+
+If you have a set of messages for a given locale, for instance **Language/en/App.php**, you can add language variants for that locale, each in its own folder, for instance **Language/en-US/App.php**.
+
+You only need to provide values for those messages that would be localized differently for that locale variant. Any missing message definitions will be automatically pulled from the main locale settings.
+
+It gets better - the localization can fall all the way back to English (**en**), in case new messages are added to the framework and you haven't had a chance to translate them yet for your locale.
+
+So, if you are using the locale `fr-CA`, then a localized message will first be sought in the **Language/fr-CA** directory, then in the **Language/fr** directory, and finally in the **Language/en** directory.
+
+### System Message Translations
+
+We have an "official" set of the system message translations in their [own repository](https://github.com/codeigniter4/translations).
+
+You could download that repository, and copy its **Language** folder into your **app** folder. The incorporated translations will be automatically picked up because the `App` namespace is mapped to your **app** folder.
+
+Alternatively, a better practice would be to run the following command inside your project:
+
+```console
+composer require codeigniter4/translations
+```
+
+The translated messages will be automatically picked up because the translations folders get mapped appropriately.
+
+### Overriding System Message Translations
+
+The framework provides [System Message Translations](#system-message-translations), and packages that you installed may also provide the message translations.
+
+If you want to override some language messages, create language files in the **app/Language** directory. Then, return only the array you want to override in the file.
+
+### Generating Translation Files via Command
+
+!!! success "Available from version 4.5.0"
+
+You can automatically generate and update translation files in your **app** folder. The command will search for uses of the `lang()` function and combine them with the current translation keys in **app/Language**, using the `defaultLocale` locale from `Config\App`. After the operation, you need to translate the language keys yourself. The command can recognize nested keys such as `File.array.nested.text`. Previously saved keys do not change.
+
+!!! note "Note"
+ Keys that `lang()` can already resolve (for example `Errors.*` from **system/Language**) are treated as already translated, so they are neither listed by `--show-new` nor written into **app/Language**.
+
+```console
+php spark lang:find
+```
+
+```php
+ [
+ 'success' => 'Text.info.success',
+ ],
+ 'paragraph' => 'Text.paragraph',
+];
+```
+
+!!! note "Note"
+ When the command scans folders, **app/Language** will be skipped.
+
+The language files generated will most likely not conform to your coding standards. It is recommended to format them. For example, run `vendor/bin/php-cs-fixer fix ./app/Language` if `php-cs-fixer` is installed.
+
+Before updating, it is possible to preview the translations found by the command:
+
+```console
+php spark lang:find --verbose --show-new
+```
+
+The detailed output of `--verbose` also shows a list of invalid keys. For example:
+
+```console
+...
+
+Files found: 10
+New translates found: 30
+Bad translates found: 5
++------------------------+---------------------------------+
+| Bad Key | Filepath |
++------------------------+---------------------------------+
+| ..invalid_nested_key.. | app/Controllers/Translation.php |
+| .invalid_key | app/Controllers/Translation.php |
+| TranslationBad | app/Controllers/Translation.php |
+| TranslationBad. | app/Controllers/Translation.php |
+| TranslationBad... | app/Controllers/Translation.php |
++------------------------+---------------------------------+
+
+All operations done!
+```
+
+For a more accurate search, specify the desired locale or directory to scan.
+
+```console
+php spark lang:find --dir Controllers/Translation --locale en --show-new
+```
+
+Detailed information can be found by running the command:
+
+```console
+php spark lang:find --help
+```
+
+#### Synchronizing Translation Files via Command
+
+!!! success "Available from version 4.6.0"
+
+You may need to create files for another language when you've finished translating for the current language. You can use the spark `lang:find` command to help with this. However, it might not detect all translations, particularly those with dynamically set parameters like `lang('App.status.' . $key, ['payload' => 'John'], 'en')`.
+
+To ensure no translations are missed, it's best to copy the completed language files and translate them manually. This approach preserves any unique keys the command might have overlooked.
+
+All you need to do is execute:
+
+```console
+// Specify the locale for new/updated translations
+php spark lang:sync --target ru
+
+// or set the original locale
+php spark lang:sync --locale en --target ru
+```
+
+As a result, you will receive files with the translation keys. If there were duplicate keys in the target locale, they are saved.
+
+!!! warning "Warning"
+ Non-matching keys in new translations are deleted!
diff --git a/docs/outgoing/response.md b/docs/outgoing/response.md
new file mode 100644
index 000000000000..48e43396a39b
--- /dev/null
+++ b/docs/outgoing/response.md
@@ -0,0 +1,596 @@
+# HTTP Responses
+
+The Response class extends the [HTTP Message Class](../incoming/message.md) with methods appropriate only for a server responding to the client that called it.
+
+## Working with the Response
+
+A Response class is instantiated for you and passed into your controllers. It can be accessed through `$this->response`. It is the same instance that `Services::response()` returns. We call it the global response instance.
+
+Many times you will not need to touch the class directly, since CodeIgniter takes care of sending the headers and the body for you. This is great if the page successfully created the content it was asked to. When things go wrong, or you need to send very specific status codes back, or even take advantage of the powerful HTTP caching, it's there for you.
+
+### Setting the Output
+
+When you need to set the output of the script directly, and not rely on CodeIgniter to automatically get it, you do it manually with the `setBody` method. This is usually used in conjunction with setting the status code of the response:
+
+```php
+response->setStatusCode(404)->setBody($body);
+```
+
+The reason phrase ('OK', 'Created', 'Moved Permanently') will be automatically added, but you can add custom reasons as the second parameter of the `setStatusCode()` method:
+
+```php
+response->setStatusCode(404, 'Nope. Not here.');
+```
+
+You can format an array into either JSON or XML and set the content type header to the appropriate MIME type with the `setJSON()` and `setXML()` methods. Typically, you will send an array of data to be converted:
+
+```php
+ true,
+ 'id' => 123,
+];
+
+return $this->response->setJSON($data);
+
+// or
+return $this->response->setXML($data);
+```
+
+### Setting Headers
+
+#### setHeader()
+
+Often, you will need to set headers for the response. The Response class makes this very simple to do, with the `setHeader()` method.
+
+The first parameter is the name of the header. The second parameter is the value, which can be either a string or an array of values that will be combined correctly when sent to the client.
+
+```php
+response->setHeader('Location', 'http://example.com')
+ ->setHeader('WWW-Authenticate', 'Negotiate');
+```
+
+Using these methods instead of the native PHP functions ensures that no headers are sent prematurely, causing errors, and makes testing possible.
+
+!!! important "Important"
+ Since v4.6.0, if you set a header using PHP's native `header()` function and then use the `Response` class to set the same header, the previous one will be overwritten.
+
+!!! note "Note"
+ This method just sets headers on the response instance. So, if you create and return another response instance (e.g., if you call `redirect()`), the headers set here will not be sent automatically.
+
+#### appendHeader()
+
+If the header exists and can have more than one value, you may use the `appendHeader()` and `prependHeader()` methods to add the value to the end or beginning of the values list, respectively. The first parameter is the name of the header, while the second is the value to append or prepend.
+
+```php
+response->setHeader('Cache-Control', 'no-cache')
+ ->appendHeader('Cache-Control', 'must-revalidate');
+```
+
+#### removeHeader()
+
+Headers can be removed from the response with the `removeHeader()` method, which takes the header name as the only parameter. This is not case-sensitive.
+
+```php
+response->removeHeader('Location');
+```
+
+## Redirect
+
+If you want to create a redirect, use the `redirect()` function.
+
+It returns a `RedirectResponse` instance. It is a different instance from the global response instance that `Services::response()` returns.
+
+!!! warning "Warning"
+ If you set Cookies or Response Headers before you call `redirect()`, they are set on the global response instance, and they are not automatically copied to the `RedirectResponse` instance. To send them, you need to call the `withCookies()` or `withHeaders()` method manually.
+
+!!! important "Important"
+ If you want to redirect, an instance of `RedirectResponse` must be returned in a method of the [Controller](../incoming/controllers.md) or the [Controller Filter](../incoming/filters.md). Note that the `__construct()` or the `initController()` method cannot return any value. If you forget to return `RedirectResponse`, no redirection will occur.
+
+### Redirect to a URI path
+
+When you want to pass a URI path (relative to baseURL), use `redirect()->to()`:
+
+```php
+// Go to specific URI path. "admin/home" is the URI path relative to baseURL.
+return redirect()->to('admin/home');
+```
+
+!!! note "Note"
+ If there is a fragment in your URL that you want to remove, you can use the refresh parameter in the method. For example, `return redirect()->to('admin/home', null, 'refresh');`.
+
+### Redirect to a Defined Route
+
+When you want to pass a [route name](../incoming/routing.md#named-routes) or `Controller::method` for [reverse routing](../incoming/routing.md#reverse-routing), use `redirect()->route()`:
+
+```php
+// Go to a named route. "user_gallery" is the route name, not a URI path.
+return redirect()->route('user_gallery');
+```
+
+An argument passed into the function is treated as a route name or `Controller::method` for reverse routing, not a relative/full URI, the same as when using `redirect()->route()`:
+
+```php
+// Go to a named/reverse-routed URI.
+return redirect('named_route');
+```
+
+### Redirect Back
+
+When you want to redirect back, use `redirect()->back()`:
+
+```php
+// Go back to the previous page.
+return redirect()->back();
+
+// Keep the old input values upon redirect so they can be used by the `old()` function.
+return redirect()->back()->withInput();
+
+// Set a flash message.
+return redirect()->back()->with('foo', 'message');
+```
+
+!!! note "Note"
+ `redirect()->back()` is not the same as the browser "back" button. It takes a visitor to "the last page viewed during the Session" when the Session is available. If the Session hasn't been loaded, or is otherwise unavailable, then a sanitized version of HTTP_REFERER will be used.
+
+### Redirect with Cookies
+
+If you set Cookies before you call `redirect()`, they are set on the global response instance, and they are not automatically copied to the `RedirectResponse` instance.
+
+To send the Cookies, you need to call the `withCookies()` method manually.
+
+```php
+// Copies all cookies from global response instance.
+return redirect()->back()->withCookies();
+```
+
+### Redirect with Headers
+
+If you set Response Headers before you call `redirect()`, they are set on the global response instance, and they are not automatically copied to the `RedirectResponse` instance.
+
+To send the Headers, you need to call the `withHeaders()` method manually.
+
+```php
+// Copies all headers from the global response instance.
+return redirect()->back()->withHeaders();
+```
+
+### Redirect Status Code
+
+The default HTTP status code for GET requests is 302. However, when using HTTP/1.1 or later, 303 is used for POST/PUT/DELETE requests and 307 for all other requests.
+
+You can specify the status code:
+
+```php
+// Redirect to a URI path relative to baseURL with status code 301.
+return redirect()->to('admin/home', 301);
+
+// Redirect to a route with status code 308.
+return redirect()->route('user_gallery', [], 308);
+
+// Redirect back with status code 302.
+return redirect()->back(302);
+```
+
+!!! note "Note"
+ Due to a bug, in v4.3.3 or previous versions, the status code of the actual redirect response might be changed even if a status code was specified. See [ChangeLog v4.3.4](../changelogs/v4.3.4.md#redirect-status-code).
+
+If you don't know the HTTP status codes for redirection, it is recommended to read [Redirections in HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections).
+
+## Force File Download
+
+The Response class provides a simple way to send a file to the client, prompting the browser to download the data to your computer. This sets the appropriate headers to make it happen.
+
+The first parameter is the **name you want to give the downloaded file**, and the second parameter is the file data.
+
+If you set the second parameter to null and `$filename` is an existing, readable file path, then its content will be read instead.
+
+If you set the third parameter to boolean true, then the actual file MIME type (based on the filename extension) will be sent, so that if your browser has a handler for that type, it can use it.
+
+Example:
+
+```php
+response->download($name, $data);
+```
+
+If you want to download an existing file from your server, you'll need to pass `null` explicitly for the second parameter:
+
+```php
+response->download('/path/to/photo.jpg', null);
+```
+
+Use the optional `setFileName()` method to change the filename as it is sent to the client's browser:
+
+```php
+response->download('awkwardEncryptedFileName.fakeExt', null)->setFileName('expenses.csv');
+```
+
+!!! note "Note"
+ The response object MUST be returned for the download to be sent to the client. This allows the response to be passed through all **after** filters before being sent to the client.
+
+### Open File in Browser
+
+Some browsers can display files such as PDF. To tell the browser to display the file instead of saving it, call the `DownloadResponse::inline()` method.
+
+```php
+response->download($name, $data)->inline();
+```
+
+## HTTP Caching
+
+Built into the HTTP specification are tools that help the client (often the web browser) cache the results. Used correctly, this can lead to a huge performance boost to your application because it will tell the client that they don't need to contact the server at all since nothing has changed. And you can't get faster than that.
+
+This is handled through the `Cache-Control` and `ETag` headers. This guide is not the proper place for a thorough introduction to the power of all the cache headers, but you can get a good understanding over at [Google Developers](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching).
+
+By default, all response objects sent through CodeIgniter have HTTP caching turned off. The options and exact circumstances are too varied for us to be able to create a good default other than turning it off. It's simple to set the Cache values to what you need, through the `setCache()` method:
+
+```php
+ 300,
+ 's-maxage' => 900,
+ 'etag' => 'abcde',
+];
+$this->response->setCache($options);
+```
+
+The `$options` array simply takes an array of key/value pairs that are, with a couple of exceptions, assigned to the `Cache-Control` header. You are free to set all of the options exactly as you need for your specific situation. While most of the options are applied to the `Cache-Control` header, it intelligently applies the `etag` and `last-modified` options to their appropriate headers.
+
+## Class Reference
+
+!!! note "Note"
+ In addition to the methods listed here, this class inherits the methods from the [Message Class](../incoming/message.md).
+
+The methods inherited from the Message Class are:
+
+- `CodeIgniter\\HTTP\\Message::getBody`
+- `CodeIgniter\\HTTP\\Message::setBody`
+- `CodeIgniter\\HTTP\\Message::populateHeaders`
+- `CodeIgniter\\HTTP\\Message::headers`
+- `CodeIgniter\\HTTP\\Message::header`
+- `CodeIgniter\\HTTP\\Message::getHeaderLine`
+- `CodeIgniter\\HTTP\\Message::setHeader`
+- `CodeIgniter\\HTTP\\Message::removeHeader`
+- `CodeIgniter\\HTTP\\Message::appendHeader`
+- `CodeIgniter\\HTTP\\Message::getProtocolVersion`
+- `CodeIgniter\\HTTP\\Message::setProtocolVersion`
+
+## CodeIgniter\HTTP
+
+### Response
+
+#### getStatusCode()
+
+- **Returns**: The current HTTP status code for this response
+- **Return type**: `int`
+
+Returns the current status code for this response. If no status code has been set, an `HTTPException` will be thrown:
+
+```php
+getStatusCode();
+```
+
+#### setStatusCode($code[, $reason=''])
+
+- **Parameters**
+ - **$code** `int` The HTTP status code
+ - **$reason** `string` An optional reason phrase.
+
+- **Returns**: The current Response instance
+
+- **Return type**: `CodeIgniter\HTTP\Response`
+
+Sets the HTTP status code that should be sent with this response:
+
+```php
+setStatusCode(404);
+```
+
+The reason phrase will be automatically generated based upon the official lists. If you need to set your own for a custom status code, you can pass the reason phrase as the second parameter:
+
+```php
+setStatusCode(230, 'Tardis initiated');
+```
+
+#### getReasonPhrase()
+
+- **Returns**: The current reason phrase.
+- **Return type**: `string`
+
+Returns the current reason phrase for this response. If no status has been set, it will return an empty string:
+
+```php
+getReasonPhrase();
+```
+
+#### setDate($date)
+
+- **Parameters**
+ - **$date** `DateTime` A DateTime instance with the time to set for this response.
+
+- **Returns**: The current response instance.
+
+- **Return type**: `CodeIgniter\HTTP\Response`
+
+Sets the date used for this response. The `$date` argument must be an instance of `DateTime`.
+
+#### setContentType($mime[, $charset='UTF-8'])
+
+- **Parameters**
+ - **$mime** `string` The content type this response represents.
+ - **$charset** `string` The character set this response uses.
+
+- **Returns**: The current response instance.
+
+- **Return type**: `CodeIgniter\HTTP\Response`
+
+Sets the content type this response represents:
+
+```php
+setContentType('text/plain');
+$response->setContentType('text/html');
+$response->setContentType('application/json');
+```
+
+By default, the method sets the character set to `UTF-8`. If you need to change this, you can pass the character set as the second parameter:
+
+```php
+setContentType('text/plain', 'x-pig-latin');
+```
+
+#### noCache()
+
+- **Returns**: The current response instance.
+- **Return type**: `CodeIgniter\HTTP\Response`
+
+Sets the `Cache-Control` header to turn off all HTTP caching. This is the default setting of all response messages:
+
+```php
+noCache();
+/*
+ * Sets the following header:
+ * Cache-Control: no-store, max-age=0, no-cache
+ */
+```
+
+#### setCache($options)
+
+- **Parameters**
+ - **$options** `array` An array of key/value cache control settings
+
+- **Returns**: The current response instance.
+
+- **Return type**: `CodeIgniter\HTTP\Response`
+
+Sets the `Cache-Control` headers, including `ETags` and `Last-Modified`. Typical keys are:
+
+- etag
+- last-modified
+- max-age
+- s-maxage
+- private
+- public
+- must-revalidate
+- proxy-revalidate
+- no-transform
+
+When passing the last-modified option, it can be either a date string or a DateTime object.
+
+#### setLastModified($date)
+
+- **Parameters**
+ - **$date** `string|DateTime` The date to set the Last-Modified header to
+
+- **Returns**: The current response instance.
+
+- **Return type**: `CodeIgniter\HTTP\Response`
+
+Sets the `Last-Modified` header. The `$date` argument can be either a string or a `DateTime` instance:
+
+```php
+setLastModified(date('D, d M Y H:i:s'));
+$response->setLastModified(\DateTime::createFromFormat('!U', $timestamp));
+```
+
+#### send(): Response
+
+- **Returns**: The current response instance.
+- **Return type**: `CodeIgniter\HTTP\Response`
+
+Tells the response to send everything back to the client. This will first send the headers, followed by the response body. For the main application response, you do not need to call this as it is handled automatically by CodeIgniter.
+
+#### setCookie($name = ''[, $value = ''[, $expire = 0[, $domain = ''[, $path = '/'[, $prefix = ''[, $secure = null[, $httponly = null[, $samesite = null]]]]]]]])
+
+- **Parameters**
+ - **$name** `array|Cookie|string` Cookie name *or* associative array of all of the parameters available to this method *or* an instance of `CodeIgniter\Cookie\Cookie`
+ - **$value** `string` Cookie value
+ - **$expire** `int` Cookie expiration time in seconds. If set to `0` the cookie will only last as long as the browser is open
+ - **$domain** `string` Cookie domain
+ - **$path** `string` Cookie path
+ - **$prefix** `string` Cookie name prefix. If set to `''`, the default value from **app/Config/Cookie.php** will be used
+ - **$secure** `bool|null` Whether to only transfer the cookie through HTTPS. If set to `null`, the default value from **app/Config/Cookie.php** will be used
+ - **$httponly** `bool|null` Whether to only make the cookie accessible for HTTP requests (no JavaScript). If set to `null`, the default value from **app/Config/Cookie.php** will be used
+ - **$samesite** `string|null` The value for the SameSite cookie parameter. If set to `''` or `null`, the default value from **app/Config/Cookie.php** will be used
+
+- **Return type**: `void`
+
+!!! note "Note"
+ Prior to v4.2.7, the default values of `$secure` and `$httponly` were `false` due to a bug, and these values from **app/Config/Cookie.php** were never used.
+
+Sets a cookie containing the values you specify to the Response instance.
+
+There are two ways to pass information to this method so that a cookie can be set: Array Method, and Discrete Parameters:
+
+**Array Method**
+
+Using this method, an associative array is passed as the first parameter:
+
+```php
+ 'The Cookie Name',
+ 'value' => 'The Value',
+ 'expire' => 86500,
+ 'domain' => '.some-domain.com',
+ 'path' => '/',
+ 'prefix' => 'myprefix_',
+ 'secure' => true,
+ 'httponly' => false,
+ 'samesite' => 'Lax',
+];
+
+$response->setCookie($cookie);
+```
+
+Only the `name` and `value` are required. To delete a cookie, set it with the `value` blank.
+
+The `expire` is set in **seconds**, which will be added to the current time. Do not include the time, but rather only the number of seconds from *now* that you wish the cookie to be valid. If the `expire` is set to zero, the cookie will only last as long as the browser is open.
+
+!!! note "Note"
+ However, if the `value` is set to an empty string and the `expire` is set to `0`, the cookie will be deleted.
+
+For site-wide cookies regardless of how your site is requested, add your URL to the `domain` starting with a period, like this: `.your-domain.com`.
+
+The `path` is usually not needed since the method sets a root path.
+
+The `prefix` is only needed if you need to avoid name collisions with other identically named cookies for your server.
+
+The `secure` flag is only needed if you want to make it a secure cookie by setting it to `true`.
+
+The `samesite` value controls how cookies are shared between domains and sub-domains. Allowed values are `'None'`, `'Lax'`, `'Strict'` or a blank string `''`. If set to a blank string, the default SameSite attribute will be set.
+
+**Discrete Parameters**
+
+If you prefer, you can set the cookie by passing data using individual parameters:
+
+```php
+setCookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httponly, $samesite);
+```
+
+#### deleteCookie($name = ''[, $domain = ''[, $path = '/'[, $prefix = '']]])
+
+- **Parameters**
+ - **$name** `mixed` Cookie name or an array of parameters
+ - **$domain** `string` Cookie domain
+ - **$path** `string` Cookie path
+ - **$prefix** `string` Cookie name prefix
+
+- **Return type**: `void`
+
+Deletes an existing cookie.
+
+!!! note "Note"
+ This also just sets a browser cookie for deleting the cookie.
+
+Only the `name` is required.
+
+The `prefix` is only needed if you need to avoid name collisions with other identically named cookies for your server.
+
+Provide a `prefix` if cookies should only be deleted for that subset. Provide a `domain` name if cookies should only be deleted for that domain. Provide a `path` name if cookies should only be deleted for that path.
+
+If any of the optional parameters are empty, then the same-named cookie will be deleted across all that apply.
+
+Example:
+
+```php
+deleteCookie($name);
+```
+
+#### hasCookie($name = ''[, $value = null[, $prefix = '']])
+
+- **Parameters**
+ - **$name** `mixed` Cookie name or an array of parameters
+ - **$value** `string` Cookie value
+ - **$prefix** `string` Cookie name prefix
+
+- **Return type**: `bool`
+
+Checks to see if the Response has a specified cookie or not.
+
+**Notes**
+
+Only the `name` is required. If a `prefix` is specified, it will be prepended to the cookie name.
+
+If no `value` is given, the method just checks for the existence of the named cookie. If a `value` is given, then the method checks that the cookie exists, and that it has the prescribed value.
+
+Example:
+
+```php
+hasCookie($name)) {
+ // ...
+}
+```
+
+#### getCookie($name = ''[, $prefix = ''])
+
+- **Parameters**
+ - **$name** `string` Cookie name
+ - **$prefix** `string` Cookie name prefix
+
+- **Return type**: `Cookie|Cookie[]|null`
+
+Returns the named cookie, if found, or `null`. If no `name` is given, returns the array of `Cookie` objects.
+
+Example:
+
+```php
+getCookie($name);
+```
+
+#### getCookies()
+
+- **Return type**: `Cookie[]`
+
+Returns all cookies currently set within the Response instance. These are any cookies that you have specifically set during the current request only.
diff --git a/docs/outgoing/table.md b/docs/outgoing/table.md
new file mode 100644
index 000000000000..5dd6c505d41a
--- /dev/null
+++ b/docs/outgoing/table.md
@@ -0,0 +1,486 @@
+# HTML Table Class
+
+The Table Class provides methods that enable you to auto-generate HTML tables from arrays or database result sets.
+
+## Using the Table Class
+
+### Initializing the Class
+
+The Table class is not provided as a service, and should be instantiated "normally", for instance:
+
+```php
+generate($data);
+```
+
+Here is an example of a table created from a database query result. The table class will automatically generate the headings based on the field names (or you can set your own headings using the `setHeading()` method described in the class reference below).
+
+```php
+query('SELECT * FROM my_table');
+
+echo $table->generate($query);
+```
+
+Here is an example showing how you might create a table using discrete parameters:
+
+```php
+setHeading('Name', 'Color', 'Size');
+
+$table->addRow('Fred', 'Blue', 'Small');
+$table->addRow('Mary', 'Red', 'Large');
+$table->addRow('John', 'Green', 'Medium');
+
+echo $table->generate();
+```
+
+Here is the same example, except instead of individual parameters, arrays are used:
+
+```php
+setHeading(['Name', 'Color', 'Size']);
+
+$table->addRow(['Fred', 'Blue', 'Small']);
+$table->addRow(['Mary', 'Red', 'Large']);
+$table->addRow(['John', 'Green', 'Medium']);
+
+echo $table->generate();
+```
+
+### Changing the Look of Your Table
+
+The Table Class permits you to set a table template with which you can specify the design of your layout. Here is the template prototype:
+
+```php
+ '',
+
+ 'thead_open' => '',
+ 'thead_close' => ' ',
+
+ 'heading_row_start' => '',
+ 'heading_row_end' => ' ',
+ 'heading_cell_start' => '',
+ 'heading_cell_end' => ' ',
+
+ 'tfoot_open' => '',
+ 'tfoot_close' => ' ',
+
+ 'footing_row_start' => '',
+ 'footing_row_end' => ' ',
+ 'footing_cell_start' => '',
+ 'footing_cell_end' => ' ',
+
+ 'tbody_open' => '',
+ 'tbody_close' => ' ',
+
+ 'row_start' => '',
+ 'row_end' => ' ',
+ 'cell_start' => '',
+ 'cell_end' => ' ',
+
+ 'row_alt_start' => '',
+ 'row_alt_end' => ' ',
+ 'cell_alt_start' => '',
+ 'cell_alt_end' => ' ',
+
+ 'table_close' => '
',
+];
+
+$table->setTemplate($template);
+```
+
+!!! note "Note"
+ You'll notice there are two sets of "row" blocks in the template. These permit you to create alternating row colors or design elements that alternate with each iteration of the row data.
+
+You are NOT required to submit a complete template. If you only need to change parts of the layout, you can simply submit those elements. In this example, only the table opening tag is being changed:
+
+```php
+ '',
+];
+
+$table->setTemplate($template);
+```
+
+You can also set defaults for these by passing an array of template settings to the Table constructor:
+
+```php
+ '',
+];
+
+$table = new \CodeIgniter\View\Table($customSettings);
+```
+
+### Synchronizing Rows with Headings
+
+!!! success "Available from version 4.4.0"
+
+The `setSyncRowsWithHeading(true)` method ensures that each data value is placed in the same column as defined in `setHeading()` if an associative array was used as a parameter. This is especially useful when dealing with data loaded via REST API where the order is not to your liking, or if the API returned too much data.
+
+If a data row contains a key that is not present in the heading, its value is filtered out. Conversely, if a data row does not have a key listed in the heading, an empty cell will be placed in its place.
+
+```php
+setHeading(['name' => 'Name', 'color' => 'Color', 'size' => 'Size'])
+ ->setSyncRowsWithHeading(true)
+ ->addRow(['color' => 'Blue', 'name' => 'Fred', 'size' => 'Small'])
+ ->addRow(['size' => 'Large', 'age' => '24', 'name' => 'Mary'])
+ ->addRow(['color' => 'Green']);
+
+echo $table->generate();
+?>
+
+
+
+
+
+ Name
+ Color
+ Size
+
+
+
+
+ Fred
+ Blue
+ Small
+
+
+ Mary
+
+ Large
+
+
+
+ Green
+
+
+
+
+```
+
+!!! important "Important"
+ You must call `setSyncRowsWithHeading(true)` and `setHeading([...])` before adding any rows via `addRow([...])` where the rearrangement of columns takes place.
+
+Using an array as input to `generate()` produces the same result:
+
+```php
+ 'Blue',
+ 'name' => 'Fred',
+ 'size' => 'Small',
+ ],
+ [
+ 'size' => 'Large',
+ 'age' => '24',
+ 'name' => 'Mary',
+ ],
+ [
+ 'color' => 'Green',
+ ],
+];
+
+$table = new \CodeIgniter\View\Table();
+
+$table->setHeading(['name' => 'Name', 'color' => 'Color', 'size' => 'Size'])
+ ->setSyncRowsWithHeading(true);
+
+echo $table->generate($data);
+```
+
+## Class Reference
+
+## CodeIgniter\View
+
+### Table
+
+#### $function = null
+
+Allows you to specify a native PHP function or a valid function array object to be applied to all cell data.
+
+```php
+setHeading('Name', 'Color', 'Size');
+$table->addRow('Fred', 'Blue ', 'Small');
+
+$table->function = 'htmlspecialchars';
+echo $table->generate();
+```
+
+In the above example, all cell data would be run through PHP's `htmlspecialchars()` function, resulting in:
+
+```
+Fred <strong>Blue</strong> Small
+```
+
+#### generate([$tableData = null])
+
+- **Parameters**
+ - **$tableData** `mixed` Data to populate the table rows with
+
+- **Returns**: HTML table
+
+- **Return type**: `string`
+
+Returns a string containing the generated table. Accepts an optional parameter which can be an array or a database result object.
+
+#### setCaption($caption)
+
+- **Parameters**
+ - **$caption** `string` Table caption
+
+- **Returns**: Table instance (method chaining)
+
+- **Return type**: `Table`
+
+Permits you to add a caption to the table.
+
+```php
+setCaption('Colors');
+```
+
+#### setHeading([$args = [] [, ...]])
+
+- **Parameters**
+ - **$args** `mixed` An array or multiple strings containing the table column titles
+
+- **Returns**: Table instance (method chaining)
+
+- **Return type**: `Table`
+
+Permits you to set the table heading. You can submit an array or discrete params:
+
+```php
+setHeading('Name', 'Color', 'Size'); // or
+
+$table->setHeading(['Name', 'Color', 'Size']);
+```
+
+#### setFooting([$args = [] [, ...]])
+
+- **Parameters**
+ - **$args** `mixed` An array or multiple strings containing the table footing values
+
+- **Returns**: Table instance (method chaining)
+
+- **Return type**: `Table`
+
+Permits you to set the table footing. You can submit an array or discrete params:
+
+```php
+setFooting('Subtotal', $subtotal, $notes); // or
+
+$table->setFooting(['Subtotal', $subtotal, $notes]);
+```
+
+#### addRow([$args = [] [, ...]])
+
+- **Parameters**
+ - **$args** `mixed` An array or multiple strings containing the row values
+
+- **Returns**: Table instance (method chaining)
+
+- **Return type**: `Table`
+
+Permits you to add a row to your table. You can submit an array or discrete params:
+
+```php
+addRow('Blue', 'Red', 'Green'); // or
+
+$table->addRow(['Blue', 'Red', 'Green']);
+```
+
+If you would like to set an individual cell's tag attributes, you can use an associative array for that cell. The associative key **data** defines the cell's data. Any other key => val pairs are added as key='val' attributes to the tag:
+
+```php
+ 'Blue', 'class' => 'highlight', 'colspan' => 2];
+$table->addRow($cell, 'Red', 'Green');
+
+?>
+
+
+Blue Red Green
+```
+
+#### makeColumns([$array = [] [, $columnLimit = 0]])
+
+- **Parameters**
+ - **$array** `array` An array containing multiple rows' data
+ - **$columnLimit** `int` Count of columns in the table
+
+- **Returns**: An array of HTML table columns
+
+- **Return type**: `array`
+
+This method takes a one-dimensional array as input and creates a multi-dimensional array with a depth equal to the number of columns desired. This allows a single array with many elements to be displayed in a table that has a fixed column count. Consider this example:
+
+```php
+makeColumns($list, 3);
+
+$table->generate($newList);
+
+?>
+
+
+
+
+ one
+ two
+ three
+
+
+ four
+ five
+ six
+
+
+ seven
+ eight
+ nine
+
+
+ ten
+ eleven
+ twelve
+
+
+```
+
+#### setTemplate($template)
+
+- **Parameters**
+ - **$template** `array` An associative array containing template values
+
+- **Returns**: true on success, false on failure
+
+- **Return type**: `bool`
+
+Permits you to set your template. You can submit a full or partial template.
+
+```php
+ '',
+];
+
+$table->setTemplate($template);
+```
+
+#### setEmpty($value)
+
+- **Parameters**
+ - **$value** `mixed` Value to put in empty cells
+
+- **Returns**: Table instance (method chaining)
+
+- **Return type**: `Table`
+
+Lets you set a default value for use in any table cells that are empty. You might, for example, set a non-breaking space:
+
+```php
+setEmpty(' ');
+```
+
+#### clear()
+
+- **Returns**: Table instance (method chaining)
+- **Return type**: `Table`
+
+Lets you clear the table heading, row data and caption. If you need to show multiple tables with different data you should call this method after each table has been generated to clear the previous table information.
+
+Example:
+
+```php
+setCaption('Preferences')
+ ->setHeading('Name', 'Color', 'Size')
+ ->addRow('Fred', 'Blue', 'Small')
+ ->addRow('Mary', 'Red', 'Large')
+ ->addRow('John', 'Green', 'Medium');
+
+echo $table->generate();
+
+$table->clear();
+
+$table->setCaption('Shipping')
+ ->setHeading('Name', 'Day', 'Delivery')
+ ->addRow('Fred', 'Wednesday', 'Express')
+ ->addRow('Mary', 'Monday', 'Air')
+ ->addRow('John', 'Saturday', 'Overnight');
+
+echo $table->generate();
+```
+
+#### setSyncRowsWithHeading(bool $orderByKey)
+
+- **Returns**: Table instance (method chaining)
+- **Return type**: `Table`
+
+Enables each row data key to be ordered by heading keys. This gives more control over data being displayed in the correct column. Make sure to set this value before calling the first `addRow()` method.
diff --git a/docs/outgoing/view_cells.md b/docs/outgoing/view_cells.md
new file mode 100644
index 000000000000..5397f02bc521
--- /dev/null
+++ b/docs/outgoing/view_cells.md
@@ -0,0 +1,383 @@
+# View Cells
+
+Many applications have small view fragments that can be repeated from page to page, or in different places on the pages. These are often help boxes, navigation controls, ads, login forms, etc. CodeIgniter lets you encapsulate the logic for these presentation blocks within View Cells. They are basically mini-views that can be included in other views. They can have logic built in to handle any cell-specific display logic. They can be used to make your views more readable and maintainable by separating the logic for each cell into its own class.
+
+## Simple and Controlled Cells
+
+CodeIgniter supports two types of View Cells: simple and controlled.
+
+**Simple View Cells** can be generated from any class and method of your choice and do not have to follow any rules, except that they must return a string.
+
+**Controlled View Cells** must be generated from a class that extends the `CodeIgniter\View\Cells\Cell` class, which provides additional capabilities that make your View Cells more flexible and faster to use.
+
+## Calling a View Cell
+
+No matter which type of View Cell you are using, you can call it from any view by using the `view_cell()` helper function.
+
+The first parameter is (1) *the name of the class and method* (Simple Cell) or (2) *the name of the class and optional method* (Controlled Cell) to call, and the second parameter is an array or string of parameters to pass to the method:
+
+```php
+// In a View.
+
+// Simple Cell
+= view_cell('App\Cells\MyClass::myMethod', ['param1' => 'value1', 'param2' => 'value2']) ?>
+
+// Controlled Cell
+= view_cell('App\Cells\MyCell', ['param1' => 'value1', 'param2' => 'value2']) ?>
+```
+
+The string that the Cell returns will be inserted into the view where the `view_cell()` function was called.
+
+### Namespace Omission
+
+!!! success "Available from version 4.3.0"
+
+If you do not include the full namespace for the class, it will assume it can be found in the `App\Cells` namespace. So, the following example would attempt to find the `MyClass` class in **app/Cells/MyClass.php**. If it is not found there, all namespaces will be scanned until it is found, searching within a **Cells** subdirectory of each namespace:
+
+```php
+// In a View.
+= view_cell('MyClass::myMethod', ['param1' => 'value1', 'param2' => 'value2']) ?>
+```
+
+### Passing Parameters as Key/Value String
+
+You can also pass the parameters along as a key/value string:
+
+```php
+// In a View.
+= view_cell('MyClass::myMethod', 'param1=value1, param2=value2') ?>
+```
+
+## Simple Cells
+
+Simple Cells are classes that return a string from the chosen method. An example of a simple Alert Message cell might look like this:
+
+```php
+{$params['message']}";
+ }
+}
+```
+
+You would call it from within a view like this:
+
+```php
+// In a View.
+= view_cell('AlertMessage::show', ['type' => 'success', 'message' => 'The user has been updated.']) ?>
+```
+
+Additionally, you can use parameter names that match the parameter variables in the method for better readability. When you use it this way, all of the parameters must always be specified in the view cell call:
+
+```php
+// In a View.
+= view_cell('Blog::recentPosts', 'category=codeigniter, limit=5') ?>
+```
+
+```php
+blogModel->where('category', $category)
+ ->orderBy('published_on', 'desc')
+ ->limit($limit)
+ ->get();
+
+ return view('recentPosts', ['posts' => $posts]);
+ }
+}
+```
+
+## Controlled Cells
+
+!!! success "Available from version 4.3.0"
+
+Controlled cells have two primary goals: (1) to make it as fast as possible to build the cell, and (2) to provide additional logic and flexibility to your views, if they need it.
+
+The class must extend `CodeIgniter\View\Cells\Cell`. It should have a view file in the same folder. By convention, the class name should be in PascalCase suffixed with `Cell` and the view should be the snake_cased version of the class name, without the suffix. For example, if you have a `MyCell` class, the view file should be `my.php`.
+
+!!! note "Note"
+ Prior to v4.3.5, the generated view file ended with `_cell.php`. Though v4.3.5 and newer will generate view files without the `_cell` suffix, existing view files will still be located and loaded.
+
+### Creating a Controlled Cell
+
+At the most basic level, all you need to implement within the class are public properties. These properties will be made available to the view file automatically.
+
+Implementing the AlertMessage from above as a Controlled Cell would look like this:
+
+```php
+">
+ = esc($message) ?>
+
+```
+
+```php
+// Called in main View:
+= view_cell('AlertMessageCell', 'type=warning, message=Failed.') ?>
+```
+
+!!! note "Note"
+ If you use typed properties, you must set the initial values:
+
+ ```php
+ view()` instead of the normal `view()` helper function:
+
+```php
+view('my/custom/view', ['extra' => 'data']);
+ }
+}
+```
+
+### Computed Properties
+
+If you need to perform additional logic for one or more properties, you can use computed properties. These require setting the property to either `protected` or `private` and implementing a public method whose name consists of the property name surrounded by `get` and `Property`:
+
+```php
+// In a View. Initialize the protected properties.
+= view_cell('AlertMessageCell', ['type' => 'note', 'message' => 'test']) ?>
+```
+
+```php
+computed = sprintf('%s - %s', $this->type, $this->message);
+ }
+
+ public function getComputedProperty(): string
+ {
+ return $this->computed;
+ }
+
+ public function getTypeProperty(): string
+ {
+ return $this->type;
+ }
+
+ public function getMessageProperty(): string
+ {
+ return $this->message;
+ }
+}
+```
+
+```php
+// app/Cells/alert_message.php
+
+
type - = esc($type) ?>
+
message - = esc($message) ?>
+
computed: = esc($computed) ?>
+
+```
+
+!!! important "Important"
+ You can't set properties that are declared as private during cell initialization. The `getDataProperty()` and `getViewProperty()` methods are not available: they are used only for internal processes.
+
+### Presentation Methods
+
+Sometimes you need to perform additional logic for the view, but you don't want to pass it as a parameter. You can implement a method that will be called from within the cell's view itself. This can help the readability of your views. You may have noticed that not only method calls are allowed in the template: the entire Cell object with public properties is available.
+
+```php
+id, $post->title);
+ }
+}
+```
+
+```php
+// app/Cells/recent_posts.php
+
+
+ = $this->linkPost($post) ?>
+
+
+```
+
+### Performing Setup Logic
+
+If you need to perform additional logic before the view is rendered, you can implement a `mount()` method. This method will be called just after the class is instantiated, and can be used to set additional properties or perform other logic:
+
+```php
+posts = model('PostModel')->orderBy('created_at', 'DESC')->findAll(10);
+ }
+}
+```
+
+You can pass additional parameters to the `mount()` method by passing them as an array to the `view_cell()` helper function. Any of the parameters sent that match a parameter name of the `mount()` method will be passed in:
+
+```php
+posts = model('PostModel')
+ ->when(
+ $categoryId,
+ static fn ($query, $categoryId) => $query->where('category_id', $categoryId),
+ )
+ ->orderBy('created_at', 'DESC')
+ ->findAll(10);
+ }
+}
+```
+
+```php
+// Called in main View:
+= view_cell('RecentPostsCell', ['categoryId' => 5]) ?>
+```
+
+## Cell Caching
+
+You can cache the results of the view cell call by passing the number of seconds to cache the data for as the third parameter. This will use the currently configured cache engine:
+
+```php
+// Cache the view for 5 minutes
+= view_cell('App\Cells\Blog::recentPosts', 'limit=5', 300) ?>
+```
+
+You can provide a custom name to use instead of the auto-generated one if you like, by passing the new name as the fourth parameter:
+
+```php
+// Cache the view for 5 minutes
+= view_cell('App\Cells\Blog::recentPosts', 'limit=5', 300, 'newcacheid') ?>
+```
diff --git a/docs/outgoing/view_decorators.md b/docs/outgoing/view_decorators.md
new file mode 100644
index 000000000000..7d6061ff3e7c
--- /dev/null
+++ b/docs/outgoing/view_decorators.md
@@ -0,0 +1,54 @@
+# View Decorators
+
+## What are View Decorators?
+
+View Decorators allow your application to modify the HTML output during the rendering process. This happens just before the output is cached, and allows you to apply custom functionality to your views.
+
+You can use View Decorators with [View Renderer](view_renderer.md) or [View Parser](view_parser.md).
+
+## Creating Decorators
+
+### Creating Decorator Class
+
+Creating your own view decorators requires creating a new class that implements `CodeIgniter\View\ViewDecoratorInterface`. This requires a single method that takes the generated HTML string, performs any modifications on it, and returns the resulting HTML.
+
+```php
+
+
+
+ My Layout
+
+
+ = $this->renderSection('content') ?>
+
+
+```
+
+The `renderSection()` method has two arguments: `$sectionName` and `$saveData`. `$sectionName` is the name of the section used by any child view to name the content section. If the boolean argument `$saveData` is set to true, the method saves data for subsequent calls. Otherwise, the method cleans the data after displaying the contents.
+
+E.g. **app/Views/welcome_message.php**:
+
+```
+
+
+
+ = $this->renderSection('page_title', true) ?>
+
+
+ = $this->renderSection('page_title') ?>
+ = $this->renderSection('content') ?>
+
+
+```
+
+!!! note "Note"
+ `$saveData` can be used since v4.4.0.
+
+## Using Layouts in Views
+
+Whenever a view wants to be inserted into a layout, it must use the `extend()` method at the top of the file:
+
+```
+= $this->extend('default') ?>
+```
+
+The `extend()` method takes the name of any view file that you wish to use. Since they are standard views, they will be located just like a view. By default, it will look in the application's View directory, but will also scan other PSR-4 defined namespaces. You can include a namespace to locate the view in a particular namespace's View directory:
+
+```
+= $this->extend('Blog\default') ?>
+```
+
+All content within a view that extends a layout must be included within `section($name)` and `endSection()` method calls. Any content between these calls will be inserted into the layout wherever the `renderSection($name)` call that matches the section name exists.
+
+E.g. **app/Views/some_view.php**:
+
+```
+= $this->extend('default') ?>
+
+= $this->section('content') ?>
+ Hello World!
+= $this->endSection() ?>
+```
+
+The `endSection()` does not need the section name. It automatically knows which one to close.
+
+Sections can contain nested sections:
+
+```
+= $this->extend('default') ?>
+
+= $this->section('content') ?>
+ Hello World!
+ = $this->section('javascript') ?>
+ let a = 'a';
+ = $this->endSection() ?>
+= $this->endSection() ?>
+```
+
+## Rendering the View
+
+Rendering the view and its layout is done exactly as any other view would be displayed within a controller:
+
+```php
+include()` to include any view partials.
+
+```
+= $this->extend('default') ?>
+
+= $this->section('content') ?>
+ Hello World!
+
+ = $this->include('sidebar') ?>
+= $this->endSection() ?>
+```
+
+When calling the `include()` method, you can pass it all of the same options that you can when rendering a normal view, including cache directives, etc.
+
+## Assign Data to Layers
+
+You can pass additional variables to your templates, just like for `view()`. If you don't use the `saveData = false` flag anywhere in the templates, then the data will be inherited for each block.
+
+!!! note "Note"
+ When using a loop, passing new variables is required.
+
+```php
+= $this->setVar('title', 'Homepage')->extend('default') ?>
+
+= $this->setVar('hideNavbar', true)->section('content') ?>
+ Items List
+
+ = $this->setData(['item' => $item])->include('item_card') ?>
+
+= $this->endSection() ?>
+```
diff --git a/docs/outgoing/view_parser.md b/docs/outgoing/view_parser.md
new file mode 100644
index 000000000000..20feeb1fe1e7
--- /dev/null
+++ b/docs/outgoing/view_parser.md
@@ -0,0 +1,934 @@
+# View Parser
+
+The View Parser can perform simple text substitution for pseudo-variables contained within your view files. It can parse simple variables or variable tag pairs.
+
+Pseudo-variable names or control constructs are enclosed in braces, like this:
+
+```
+
+
+ {blog_title}
+
+
+ {blog_heading}
+
+ {blog_entries}
+ {title}
+ {body}
+ {/blog_entries}
+
+
+
+```
+
+These variables are not actual PHP variables, but rather plain text representations that allow you to eliminate PHP from your templates (view files).
+
+!!! note "Note"
+ CodeIgniter does **not** require you to use this class since using pure PHP in your view pages (for instance using the [View renderer](view_renderer.md)) lets them run a little faster. However, some developers prefer to use some form of template engine if they work with designers who might find working with PHP confusing.
+
+## Using the View Parser Class
+
+The simplest method to load the parser class is through its service:
+
+```php
+ 'My Blog Title',
+ 'blog_heading' => 'My Blog Heading',
+];
+
+return $parser->setData($data)->render('blog_template');
+```
+
+View parameters are passed to `setData()` as an associative array of data to be replaced in the template.
+
+In the above example, the template would contain two variables: `{blog_title}` and `{blog_heading}`.
+
+The first parameter to `render()` contains the name of the template, where `blog_template` is the name of your view template file.
+
+!!! important "Important"
+ If the file extension is omitted, then the views are expected to end with the .php extension.
+
+### Parser Configuration Options
+
+Several options can be passed to the `render()` or `renderString()` methods.
+
+- `cache` - the time in seconds to save a view's results; ignored for `renderString()`
+- `cache_name` - the ID used to save/retrieve a cached view result; defaults to the viewpath; ignored for `renderString()`
+- `saveData` - true if the view data parameters should be retained for subsequent calls; default is **true**
+- `cascadeData` - true if pseudo-variable settings should be passed on to nested substitutions; default is **true**
+- `restrictConditionals` - true to allow only simple expressions in conditionals; see [Restricting Conditionals](#restricting-conditionals). Defaults to the `Config\View::$restrictParserConditionals` value (**false**)
+
+```php
+render('blog_template', [
+ 'cache' => HOUR,
+ 'cache_name' => 'something_unique',
+]);
+```
+
+## Substitution Variations
+
+There are three types of substitution supported: simple, looping, and nested. Substitutions are performed in the same sequence that pseudo-variables were added.
+
+The **simple substitution** performed by the parser is a one-to-one replacement of pseudo-variables where the corresponding data parameter has either a scalar or string value, as in this example:
+
+```php
+{blog_title} ';
+$data = ['blog_title' => 'My ramblings'];
+
+return $parser->setData($data)->renderString($template);
+// Result: My ramblings
+```
+
+Substitution values are treated as data, not as template code. Any Parser syntax within a value is rendered literally and is not processed by later substitutions. This also applies to values used inside variable pairs - the pairs themselves must be defined in the template.
+
+The `Parser` takes substitution a lot further with "variable pairs", used for nested substitutions or looping, and with some advanced constructs for conditional substitution.
+
+When the parser executes, it will generally:
+
+- handle any conditional substitutions
+- handle any nested/looping substitutions
+- handle the remaining single substitutions
+
+### Loop Substitutions
+
+A loop substitution happens when the value for a pseudo-variable is a sequential array of arrays, like an array of row settings.
+
+The above example code allows simple variables to be replaced. What if you would like an entire block of variables to be repeated, with each iteration containing new values? Consider the template example we showed at the top of the page:
+
+```
+
+
+ {blog_title}
+
+
+ {blog_heading}
+
+ {blog_entries}
+ {title}
+ {body}
+ {/blog_entries}
+
+
+
+```
+
+In the above code you'll notice a pair of variables: `{blog_entries}` data... `{/blog_entries}`. In a case like this, the entire chunk of data between these pairs would be repeated multiple times, corresponding to the number of rows in the "blog_entries" element of the parameters array.
+
+Parsing variable pairs is done using the identical code shown above to parse single variables, except that you will add a multi-dimensional array corresponding to your variable pair data. Consider this example:
+
+```php
+ 'My Blog Title',
+ 'blog_heading' => 'My Blog Heading',
+ 'blog_entries' => [
+ ['title' => 'Title 1', 'body' => 'Body 1'],
+ ['title' => 'Title 2', 'body' => 'Body 2'],
+ ['title' => 'Title 3', 'body' => 'Body 3'],
+ ['title' => 'Title 4', 'body' => 'Body 4'],
+ ['title' => 'Title 5', 'body' => 'Body 5'],
+ ],
+];
+
+return $parser->setData($data)->render('blog_template');
+```
+
+The value for the pseudo-variable `blog_entries` is a sequential array of associative arrays. The outer level does not have keys associated with each of the nested "rows".
+
+If your "pair" data is coming from a database result, which is already a multi-dimensional array, you can simply use the database `getResultArray()` method:
+
+```php
+query('SELECT * FROM blog');
+
+$data = [
+ 'blog_title' => 'My Blog Title',
+ 'blog_heading' => 'My Blog Heading',
+ 'blog_entries' => $query->getResultArray(),
+];
+
+return $parser->setData($data)->render('blog_template');
+```
+
+If the array you are trying to loop over contains objects instead of arrays, the parser will first look for a `toArray()` method on the object. If it exists, that method will be called and the resulting array is then looped over just as described above. If no `toArray()` method exists, the object will be cast as an array. Public properties are available by name, while non-public properties have visibility-specific prefixes in their array keys.
+
+This is especially useful with Entity classes, whose `toArray()` method returns their attributes with any configured casts and data mappings applied.
+
+### Nested Substitutions
+
+A nested substitution happens when the value for a pseudo-variable is an associative array of values, like a record from a database:
+
+```php
+ 'My Blog Title',
+ 'blog_heading' => 'My Blog Heading',
+ 'blog_entries' => [
+ [
+ 'title' => 'Title 1',
+ 'body' => 'Body 1',
+ ],
+ ],
+];
+
+return $parser->setData($data)->render('blog_template');
+```
+
+The value for the pseudo-variable `blog_entries` is an associative array. The key/value pairs defined inside it will be exposed inside the variable pair loop for that variable.
+
+A **blog_template.php** that might work for the above:
+
+```
+{blog_title} - {blog_heading}
+{blog_entries}
+
+{/blog_entries}
+```
+
+If you would like the other pseudo-variables accessible inside the `blog_entries` scope, then make sure that the `cascadeData` option is set to true.
+
+### Comments
+
+You can place comments in your templates that will be ignored and removed during parsing by wrapping the comments in `{# #}` symbols.
+
+```
+{# This comment is removed during parsing. #}
+{blog_entry}
+
+{/blog_entry}
+```
+
+### Cascading Data
+
+With both a nested and a loop substitution, you have the option of cascading data pairs into the inner substitution.
+
+The following example is not impacted by cascading:
+
+```php
+ 'George',
+ 'locations' => [
+ ['city' => 'Red City', 'planet' => 'Mars'],
+ ],
+];
+
+return $parser->setData($data)->renderString($template);
+// Result: George lives in Red City on Mars.
+```
+
+This example gives different results, depending on cascading:
+
+```php
+ 'George',
+ 'locations' => [
+ ['city' => 'Red City', 'planet' => 'Mars'],
+ ],
+];
+
+return $parser->setData($data)->renderString($template, ['cascadeData' => false]);
+// Result: {name} lives in Red City on Mars.
+
+// or
+
+return $parser->setData($data)->renderString($template, ['cascadeData' => true]);
+// Result: George lives in Red City on Mars.
+```
+
+### Preventing Parsing
+
+You can specify portions of the page to not be parsed with the `{noparse}` `{/noparse}` tag pair. Anything in this section will stay exactly as it is, with no variable substitution, looping, etc., happening to the markup between the brackets.
+
+```
+{noparse}
+ Untouched Code
+{/noparse}
+```
+
+### Conditional Logic
+
+The Parser class supports some basic conditionals to handle `if`, `else`, and `elseif` syntax. All `if` blocks must be closed with an `endif` tag:
+
+```
+{if $role=='admin'}
+ Welcome, Admin!
+{endif}
+```
+
+This simple block is converted to the following during parsing:
+
+```php
+
+ Welcome, Admin!
+
+```
+
+All variables used within if statements must have been previously set with the same name. Other than that, it is treated exactly like a standard PHP conditional, and all standard PHP rules would apply here. You can use any of the comparison operators you would normally, like `==`, `===`, `!==`, `<`, `>`, etc.
+
+```
+{if $role=='admin'}
+ Welcome, Admin
+{elseif $role=='moderator'}
+ Welcome, Moderator
+{else}
+ Welcome, User
+{endif}
+```
+
+Values passed through `setData()` can control a conditional in a trusted template:
+
+```php
+setData(['role' => $userData['role']]);
+
+echo $parser->renderString('{if $role === "admin"}Admin{endif}');
+```
+
+The value of `$role` is checked by the condition; it is not parsed as PHP source.
+
+!!! warning "Warning"
+ Conditionals are evaluated as PHP expressions using `eval()`. Anyone who can edit template source can execute PHP code through an `{if ...}` or `{elseif ...}` tag. Escaping literal PHP tags does not make an untrusted template safe to render. If less-trusted users can edit templates, for example email templates composed in your application, enable [restricted conditionals](#restricting-conditionals).
+
+#### Restricting Conditionals
+
+!!! success "Available from version 4.7.5"
+
+Conditionals are not restricted by default. Enable restriction whenever less-trusted users can edit template source. When restricted, a condition may only contain:
+
+- variables, except `$this`
+- string and number literals, and `true`, `false` and `null`
+- the arithmetic operators `+`, `-`, `*`, `/`, `%` and `**`, including unary `+` and `-`
+- the comparison operators `==`, `!=`, `<>`, `===`, `!==`, `<`, `>`, `<=` and `>=`
+- the logical operators `&&`, `||`, `and`, `or` and `!`
+- parentheses for grouping
+
+For example, `{if $vip}`, `{if $role == 'admin'}` and `{if $total > 100 && ($country == 'GB' || !$guest)}` are allowed. Negative numbers and simple arithmetic, such as `{if $balance < -10}` or `{if $total + 5 >= 100}`, are also allowed. Anything else, such as function calls, constants, property or array access, assignment, concatenation and string interpolation, throws a `ViewException` before the template is evaluated.
+
+To restrict conditionals for a single call, pass the `restrictConditionals` option:
+
+```php
+setData(['first_name' => $user->first_name])
+ ->renderString($emailTemplate, ['restrictConditionals' => true]);
+```
+
+To restrict conditionals by default, set `$restrictParserConditionals` to `true` in **app/Config/View.php**:
+
+```php
+render('dashboard', ['restrictConditionals' => false]);
+```
+
+When a plugin or filter renders another template using the same Parser instance, the nested render inherits any active restriction. It cannot disable the outer render's restriction, even with `restrictConditionals` set to `false`. The restriction is restored to its previous state when the render finishes or throws an exception, so it does not affect later independent renders.
+
+!!! note "Note"
+ Restricting conditionals only limits `{if ...}` and `{elseif ...}` tags. Pseudo-variables in less-trusted templates can still reference any view data, and `{! !}` tags output data without escaping. Only pass data the template author is allowed to see.
+
+#### Changing the Conditional Delimiters
+
+If you have JavaScript code like the following in your templates, the Parser raises a syntax error because there are strings that can be interpreted as a conditional:
+
+```
+
+```
+
+In that case, you can change the delimiters for conditionals with the `setConditionalDelimiters()` method to avoid misinterpretations:
+
+```php
+setConditionalDelimiters('{%', '%}');
+```
+
+In this case, you will write the code in your template like this:
+
+```
+{% if $role=='admin' %}
+ Welcome, Admin
+{% else %}
+ Welcome, User
+{% endif %}
+```
+
+### Escaping Data
+
+Variable substitutions without filters are escaped by default to help prevent XSS attacks on your pages. CodeIgniter's `esc()` method supports several contexts, such as `html`, `attr`, and `css`. If no context is specified, the Parser uses `html`. You can specify the context with the `esc` filter:
+
+```
+{ user_styles | esc(css) }
+{ title }
+```
+
+When a substitution uses one or more filters, the Parser does not add an `esc` filter automatically. This also applies when a context is specified with `setData()` or `setVar()`. Add `esc` explicitly when filtering untrusted data. Filters run from left to right, so its position matters:
+
+```
+{ title|capitalize|esc }
+{ body|esc|nl2br }
+```
+
+In the second example, `esc` escapes the input before `nl2br` adds HTML ` ` tags. Escaping after `nl2br` would display those tags as text. Unknown filter names are ignored, but still prevent automatic escaping; check filter names carefully.
+
+To disable automatic escaping for a substitution without filters, add exclamation marks to its delimiters:
+
+```
+{! unescaped_var !}
+```
+
+### Filters
+
+Any single variable substitution can have one or more filters applied to it to modify the way it is presented. These are not intended to drastically change the output, but to provide ways to reuse the same variable data with different presentations. The `esc` filter discussed above is one example. Dates are another common use case, where you might need to format the same data differently in several sections on the same page.
+
+Filters are commands that come after the pseudo-variable name, and are separated by the pipe symbol, `|`:
+
+```
+// -55 is displayed as 55
+{ value|abs }
+```
+
+If the filter takes any arguments, they must be separated by commas and enclosed in parentheses:
+
+```
+{ created_at|date(Y-m-d) }
+```
+
+Multiple filters can be applied to the value by piping multiple ones together. They are processed in order, from left to right:
+
+```
+{ created_at|date_modify(+5 days)|date(Y-m-d) }
+```
+
+#### Provided Filters
+
+The following filters are available when using the parser:
+
+| Filter | Arguments | Description | Example |
+|----|----|----|----|
+| abs | | Displays the absolute value of a number. | `{ v|abs }` |
+| capitalize | | Displays the string in sentence case: all lowercase with first letter capitalized. | `{ v|capitalize }` |
+| date | format (Y-m-d) | A PHP **date**-compatible formatting string. | `{ v|date(Y-m-d) }` |
+| date_modify | value to add / subtract | A **strtotime** compatible string to modify the date, like `+5 day` or `-1 week`. | `{ v|date_modify(+1 day) }` |
+| default | default value | Displays the default value if the variable is [empty()](https://www.php.net/manual/en/function.empty.php). | `{ v|default(just in case) }` |
+| esc | html, attr, css, js | Specifies the context to escape the data. | `{ v|esc(attr) }` |
+| excerpt | phrase, radius | Returns the text within a radius of words from a given phrase. Same as **excerpt** helper function. | `{ v|excerpt(green giant, 20) }` |
+| highlight | phrase | Highlights a given phrase within the text using ` ` tags. | `{ v|highlight(view parser) }` |
+| highlight_code | | Highlights code samples with HTML/CSS. | `{ v|highlight_code }` |
+| limit_chars | limit | Limits the number of characters to $limit. | `{ v|limit_chars(100) }` |
+| limit_words | limit | Limits the number of words to $limit. | `{ v|limit_words(20) }` |
+| local_currency | currency, locale, fraction | Displays a localized version of a currency. "currency" value is any 3-letter ISO 4217 currency code. | `{ v|local_currency(EUR,en_US) }` |
+| local_number | type, precision, locale | Displays a localized version of a number. "type" can be one of: decimal, currency, percent, scientific, spellout, ordinal, duration. | `{ v|local_number(decimal,2,en_US) }` |
+| lower | | Converts a string to lowercase. | `{ v|lower }` |
+| nl2br | | Replaces all newline characters (`\n`) with an HTML ` ` tag. | `{ v|nl2br }` |
+| number_format | places | Wraps PHP **number_format** function for use within the parser. | `{ v|number_format(3) }` |
+| prose | | Takes a body of text and uses the **auto_typography()** method to turn it into prettier, easier-to-read prose. | `{ v|prose }` |
+| round | places, type | Rounds a number to the specified places. Types of **ceil** and **floor** can be passed to use those functions instead. | `{ v|round(3) }` `{ v|round(ceil) }` |
+| strip_tags | allowed chars | Wraps PHP **strip_tags**. Can accept a string of allowed tags. | `{ v|strip_tags( ) }` |
+| title | | Displays a "title case" version of the string, with all lowercase, and each word capitalized. | `{ v|title }` |
+| upper | | Displays the string in all uppercase. | `{ v|upper }` |
+
+See [PHP's NumberFormatter](https://www.php.net/manual/en/numberformatter.create.php) for details relevant to the "local_number" filter.
+
+#### Custom Filters
+
+You can easily create your own filters by editing **app/Config/View.php** and adding new entries to the `$filters` array. Each key is the name by which the filter is called in the view, and its value is any valid PHP callable:
+
+```php
+ '\Some\Class::methodName',
+ 'str_repeat' => 'str_repeat', // native php function
+ ];
+
+ // ...
+}
+```
+
+Closures are also supported. When configuring a closure in **app/Config/View.php**, assign it in the constructor.
+
+### Parser Plugins
+
+Plugins allow you to extend the parser, adding custom features for each project. They can be any PHP callable, making them very simple to implement. Within templates, plugins are specified by `{+ +}` tags:
+
+```
+{+ foo +} inner content {+ /foo +}
+```
+
+This example shows a plugin named **foo**. It can manipulate any of the content between its opening and closing tags. In this example, it could work with the text " inner content ". Plugins are processed before any pseudo-variable replacements happen.
+
+The output of built-in plugins is treated as rendered data. Parser syntax within that output is not processed by later plugins or variable substitutions. Custom plugins can still return template code for further processing and must not introduce untrusted data as Parser syntax.
+
+While plugins will often consist of tag pairs, as shown above, they can also be a single tag, with no closing tag:
+
+```
+{+ foo +}
+```
+
+Opening tags can also contain parameters that can customize how the plugin works. The parameters are represented as key/value pairs:
+
+```
+{+ foo bar=2 baz="x y" +}
+```
+
+Parameters can also be single values:
+
+```
+{+ include somefile.php +}
+```
+
+#### Provided Plugins
+
+The following plugins are available when using the parser:
+
+| Plugin | Arguments | Description | Example |
+|----|----|----|----|
+| current_url | | Alias for the current_url helper function. | `{+ current_url +}` |
+| previous_url | | Alias for the previous_url helper function. | `{+ previous_url +}` |
+| siteURL | | Alias for the site_url helper function. | `{+ siteURL "login" +}` |
+| mailto | email, title, attributes | Alias for the mailto helper function. | `{+ mailto email=foo@example.com title="Stranger Things" +}` |
+| safe_mailto | email, title, attributes | Alias for the safe_mailto helper function. | `{+ safe_mailto email=foo@example.com title="Stranger Things" +}` |
+| lang | language string | Alias for the lang helper function. | `{+ lang number.terabyteAbbr +}` |
+| validation_errors | fieldname(optional) | Returns either error string for the field (if specified) or all validation errors. | `{+ validation_errors +} , {+ validation_errors field="email" +}` |
+| route | route name | Alias for the route_to helper function. | `{+ route "login" +}` |
+| csp_script_nonce | | Alias for the csp_script_nonce helper function. | `{+ csp_script_nonce +}` |
+| csp_style_nonce | | Alias for the csp_style_nonce helper function. | `{+ csp_style_nonce +}` |
+
+#### Registering a Plugin
+
+At its simplest, all you need to do to register a new plugin and make it ready for use is to add it to **app/Config/View.php**, under the `$plugins` array. The key is the name of the plugin that is used within the template file. The value is any valid PHP callable, including static class methods:
+
+```php
+ '\Some\Class::methodName',
+ ];
+
+ // ...
+}
+```
+
+You can also use closures, but these can only be defined in the config file's constructor:
+
+```php
+plugins['bar'] = static fn (array $params = []) => $params[0] ?? '';
+
+ parent::__construct();
+ }
+
+ // ...
+}
+```
+
+If the callable is on its own, it is treated as a single tag, not an open/close one. It will be replaced by the return value from the plugin:
+
+```php
+ '\Some\Class::methodName',
+ ];
+
+ // ...
+}
+
+/*
+ * Tag is replaced by the return value of Some\Class::methodName() static function.
+ * {+ foo +}
+ */
+```
+
+If the callable is wrapped in an array, it is treated as an open/close tag pair that can operate on any of the content between its tags:
+
+```php
+ ['\Some\Class::methodName'],
+ ];
+
+ // ...
+}
+
+// {+ foo +} inner content {+ /foo +}
+```
+
+## Usage Notes
+
+If you include substitution parameters that are not referenced in your template, they are ignored:
+
+```php
+ 'Mr',
+ 'firstname' => 'John',
+ 'lastname' => 'Doe',
+];
+
+return $parser->setData($data)->renderString($template);
+// Result: Hello, John Doe
+```
+
+If you do not include a substitution parameter that is referenced in your template, the original pseudo-variable is shown in the result:
+
+```php
+ 'Mr',
+ 'firstname' => 'John',
+ 'lastname' => 'Doe',
+];
+
+return $parser->setData($data)->renderString($template);
+// Result: Hello, John {initials} Doe
+```
+
+If you provide a string substitution parameter when an array is expected, i.e., for a variable pair, the substitution is done for the opening variable pair tag, but the closing variable pair tag is not rendered properly:
+
+```php
+ 'Mr',
+ 'firstname' => 'John',
+ 'lastname' => 'Doe',
+ 'titles' => [
+ ['degree' => 'BSc'],
+ ['degree' => 'PhD'],
+ ],
+];
+
+return $parser->setData($data)->renderString($template);
+// Result: Hello, John Doe (Mr{degree} {/degrees})
+```
+
+### View Fragments
+
+You do not have to use variable pairs to get the effect of iteration in your views. It is possible to use a view fragment for what would be inside a variable pair, and to control the iteration in your controller instead of in the view.
+
+An example with the iteration controlled in the view:
+
+```
+$template = '{menuitems}
+ {title}
+{/menuitems} ';
+
+$data = [
+ 'menuitems' => [
+ ['title' => 'First Link', 'link' => '/first'],
+ ['title' => 'Second Link', 'link' => '/second'],
+ ]
+];
+
+return $parser->setData($data)->renderString($template);
+```
+
+Result:
+
+```
+
+```
+
+An example with the iteration controlled in the controller, using a view fragment:
+
+```php
+{title} ';
+$data1 = [
+ ['title' => 'First Link', 'link' => '/first'],
+ ['title' => 'Second Link', 'link' => '/second'],
+];
+
+foreach ($data1 as $menuItem) {
+ $temp .= $parser->setData($menuItem)->renderString($template1);
+}
+
+$template2 = '';
+$data = [
+ 'menuitems' => $temp,
+];
+
+return $parser->setData($data)->renderString($template2);
+```
+
+Result:
+
+```
+
+```
+
+## Class Reference
+
+## CodeIgniter\View
+
+### Parser
+
+#### render($view[, $options[, $saveData]])
+
+- **Parameters**
+ - **$view** `string` File name of the view source
+ - **$options** `array` Array of options, as key/value pairs
+ - **$saveData** `boolean` If true, will save data for use with any other calls. If false, will clean the data after rendering the view.
+
+- **Returns**: The rendered text for the chosen view
+
+- **Return type**: `string`
+
+Builds the output based upon a file name and any data that has already been set:
+
+```php
+render('myview');
+```
+
+Options supported:
+
+- `cache` - the time in seconds to save a view's results
+- `cache_name` - the ID used to save/retrieve a cached view result; defaults to the viewpath
+- `cascadeData` - true if the data pairs in effect when a nested or loop substitution occurs should be propagated
+- `saveData` - true if the view data parameter should be retained for subsequent calls
+
+Any conditional substitutions are performed first, then remaining substitutions are performed for each data pair.
+
+#### renderString($template[, $options[, $saveData]])
+
+- **Parameters**
+ - **$template** `string` Trusted view source provided as a string
+ - **$options** `array` Array of options, as key/value pairs
+ - **$saveData** `boolean` If true, will save data for use with any other calls. If false, will clean the data after rendering the view.
+
+- **Returns**: The rendered text for the chosen view
+
+- **Return type**: `string`
+
+Builds the output based upon a provided template source and any data that has already been set:
+
+```php
+renderString('');
+```
+
+Never build the template source from user-provided content. Pass it as data through `setData()` instead, or [restrict conditionals](#restricting-conditionals) if users are meant to edit the template:
+
+```php
+renderString('Hello ' . $name);
+
+// Safe: $name is passed as data.
+echo $parser->setData(['name' => $name])->renderString('Hello {name}');
+```
+
+Options supported, and behavior, as above.
+
+#### setData([$data[, $context = null]])
+
+- **Parameters**
+ - **$data** `array` Array of view data strings, as key/value pairs
+ - **$context** `string` The context to use for data escaping.
+
+- **Returns**: The Renderer, for method chaining
+
+- **Return type**: `CodeIgniter\View\RendererInterface`
+
+Sets several pieces of view data at once:
+
+```php
+setData(['name' => 'George', 'position' => 'Boss']);
+```
+
+Supported escape contexts: html, css, js, url, attr or raw. If 'raw', no escaping will happen.
+
+#### setVar($name[, $value = null[, $context = null]])
+
+- **Parameters**
+ - **$name** `string` Name of the view data variable
+ - **$value** `mixed` The value of this view data
+ - **$context** `string` The context to use for data escaping.
+
+- **Returns**: The Renderer, for method chaining
+
+- **Return type**: `CodeIgniter\View\RendererInterface`
+
+Sets a single piece of view data:
+
+```php
+setVar('name', 'Joe', 'html');
+```
+
+Supported escape contexts: html, css, js, url, attr or raw. If 'raw', no escaping will happen.
+
+#### setDelimiters($leftDelimiter = '{', $rightDelimiter = '}')
+
+- **Parameters**
+ - **$leftDelimiter** `string` Left delimiter for substitution fields
+ - **$rightDelimiter** `string` Right delimiter for substitution fields
+
+- **Returns**: The Renderer, for method chaining
+
+- **Return type**: `CodeIgniter\View\RendererInterface`
+
+Override the substitution field delimiters:
+
+```php
+setDelimiters('[', ']');
+```
+
+#### setConditionalDelimiters($leftDelimiter = '{', $rightDelimiter = '}')
+
+- **Parameters**
+ - **$leftDelimiter** `string` Left delimiter for conditionals
+ - **$rightDelimiter** `string` Right delimiter for conditionals
+
+- **Returns**: The Renderer, for method chaining
+
+- **Return type**: `CodeIgniter\View\RendererInterface`
+
+Override the conditional delimiters:
+
+```php
+setConditionalDelimiters('{%', '%}');
+```
diff --git a/docs/outgoing/view_renderer.md b/docs/outgoing/view_renderer.md
new file mode 100644
index 000000000000..3ea5048be9c7
--- /dev/null
+++ b/docs/outgoing/view_renderer.md
@@ -0,0 +1,185 @@
+# View Renderer
+
+## Using the View Renderer
+
+The `view()` function is a convenience function that grabs an instance of the `renderer` service, sets the data, and renders the view. While this is often exactly what you want, you may find times where you want to work with it more directly. In that case you can access the View service directly:
+
+```php
+$view = service('renderer');
+```
+
+Alternatively, if you are not using the `View` class as your default renderer, you can instantiate it directly:
+
+```php
+$view = new \CodeIgniter\View\View();
+```
+
+!!! important "Important"
+ You should create services only within controllers. If you need access to the View class from a library, you should set that as a dependency in your library's constructor.
+
+Then you can use any of the three standard methods that it provides: `render()`, `setVar()` and `setData()`.
+
+### What It Does
+
+The `View` class processes conventional HTML/PHP scripts stored in the application's view path, after extracting view parameters into PHP variables, accessible inside the scripts. This means that your view parameter names need to be legal PHP variable names.
+
+The View class uses an associative array internally to accumulate view parameters until you call its `render()` method. This means that your parameter (or variable) names need to be unique, or a later variable setting will override an earlier one.
+
+This also impacts escaping parameter values for different contexts inside your script. You will have to give each escaped value a unique parameter name.
+
+No special meaning is attached to parameters whose value is an array. It is up to you to process the array appropriately in your PHP code.
+
+### Setting View Parameters
+
+The `setVar()` method sets a view parameter.
+
+```php
+$view->setVar('name', 'Joe', 'html');
+```
+
+The `setData()` method sets multiple view parameters at once.
+
+```php
+$view->setData(['name' => 'George', 'position' => 'Boss']);
+```
+
+### Method Chaining
+
+The `setVar()` and `setData()` methods are chainable, allowing you to combine a number of different calls together in a chain:
+
+```php
+$view->setVar('one', $one)
+ ->setVar('two', $two)
+ ->render('myView');
+```
+
+### Escaping Data
+
+When you pass data to the `setVar()` and `setData()` functions, you have the option to escape the data to protect against cross-site scripting attacks. As the last parameter in either method, you can pass the desired context to escape the data for. See below for context descriptions.
+
+If you don't want the data to be escaped, you can pass `null` or `'raw'` as the final parameter to each function:
+
+```php
+$view->setVar('one', $one, 'raw');
+```
+
+If you choose not to escape data, or you are passing in an object instance, you can manually escape the data within the view with the `esc()` function. The first parameter is the string to escape. The second parameter is the context to escape the data for (see below):
+
+```
+= esc($object->getStat()) ?>
+```
+
+#### Escaping Contexts
+
+By default, the `esc()` and, in turn, the `setVar()` and `setData()` functions assume that the data you want to escape is intended to be used within standard HTML. However, if the data is intended for use in JavaScript, CSS, or in an href attribute, you would need different escaping rules to be effective. You can pass in the name of the context as the second parameter. Valid contexts are `'html'`, `'js'`, `'css'`, `'url'`, and `'attr'`:
+
+```
+Some Link
+
+
+
+
+```
+
+### View Renderer Options
+
+Several options can be passed to the `render()` or `renderString()` methods:
+
+- `$options`
+ - `cache` - the time in seconds to save a view's results; ignored for `renderString()`.
+ - `cache_name` - the ID used to save/retrieve a cached view result; defaults to the `$viewPath`; ignored for `renderString()`.
+ - `debug` - can be set to false to disable the addition of debug code for the [Debug Toolbar](../testing/debugging.md#the-debug-toolbar).
+- `$saveData` - true if the view data parameters should be retained for subsequent calls.
+
+!!! note "Note"
+ `$saveData` as defined by the interface must be a boolean, but implementing classes (like `View` below) may extend this to include `null` values.
+
+## Class Reference
+
+## CodeIgniter\View
+
+### View
+
+#### render($view[, $options[, $saveData = false]])
+
+- **Parameters**
+ - **$view** `string` File name of the view source
+ - **$options** `array` Array of options, as key/value pairs
+ - **$saveData** `boolean|null` If true, will save data for use with any other calls. If false, will clean the data after rendering the view. If null, uses the config setting.
+
+- **Returns**: The rendered text for the chosen view
+
+- **Return type**: `string`
+
+Builds the output based upon a file name and any data that has already been set:
+
+```php
+echo $view->render('myview');
+```
+
+#### renderString($view[, $options[, $saveData = false]])
+
+- **Parameters**
+ - **$view** `string` Contents of the view to render, executed as PHP code
+ - **$options** `array` Array of options, as key/value pairs
+ - **$saveData** `boolean|null` If true, will save data for use with any other calls. If false, will clean the data after rendering the view. If null, uses the config setting.
+
+- **Returns**: The rendered text for the chosen view
+
+- **Return type**: `string`
+
+Builds the output based upon a view fragment and any data that has already been set:
+
+```php
+echo $view->renderString('My Sharona
');
+```
+
+!!! warning "Warning"
+ `renderString()` executes the given view contents as PHP code, just like a view file. Treat them as trusted code: never pass contents that less-trusted users can edit, even if they are stored in your database. Escaping or validating such contents does not make them safe to execute. Pass user-provided values as view data instead.
+
+#### setData([$data[, $context = null]])
+
+- **Parameters**
+ - **$data** `array` Array of view data strings, as key/value pairs
+ - **$context** `string` The context to use for data escaping.
+
+- **Returns**: The Renderer, for method chaining
+
+- **Return type**: `CodeIgniter\View\RendererInterface`
+
+Sets several pieces of view data at once:
+
+```php
+$view->setData(['name' => 'George', 'position' => 'Boss']);
+```
+
+Supported escape contexts: `html`, `css`, `js`, `url`, `attr` or `raw`. If `'raw'`, no escaping will happen.
+
+Each call adds to the array of data that the object is accumulating, until the view is rendered.
+
+#### setVar($name[, $value = null[, $context = null]])
+
+- **Parameters**
+ - **$name** `string` Name of the view data variable
+ - **$value** `mixed` The value of this view data
+ - **$context** `string` The context to use for data escaping.
+
+- **Returns**: The Renderer, for method chaining
+
+- **Return type**: `CodeIgniter\View\RendererInterface`
+
+Sets a single piece of view data:
+
+```php
+$view->setVar('name', 'Joe', 'html');
+```
+
+Supported escape contexts: `html`, `css`, `js`, `url`, `attr` or `raw`. If `'raw'`, no escaping will happen.
+
+If you use a view data variable that you have previously used for this object, the new value will replace the existing one.
diff --git a/docs/outgoing/views.md b/docs/outgoing/views.md
new file mode 100644
index 000000000000..8ee71bcfd87b
--- /dev/null
+++ b/docs/outgoing/views.md
@@ -0,0 +1,306 @@
+# Views
+
+A view is simply a web page, or a page fragment, like a header, footer, sidebar, etc. In fact, views can flexibly be embedded within other views (within other views, etc.) if you need this type of hierarchy.
+
+Views are never called directly. They must be loaded by a controller or [view route](../incoming/routing.md#view-routes).
+
+Remember that in an MVC framework, the Controller acts as the traffic cop, so it is responsible for fetching a particular view. If you have not read the [Controllers](../incoming/controllers.md) page, you should do so before continuing.
+
+Using the example controller you created in the controller page, let's add a view to it.
+
+## Creating a View
+
+Using your text editor, create a file called **blog_view.php** and put this in it:
+
+```
+
+
+ My Blog
+
+
+ Welcome to my Blog!
+
+
+```
+
+Then save the file in your **app/Views** directory.
+
+## Displaying a View
+
+To load and display a particular view file, you will use the `view()` function in your controller, as in the following code:
+
+```php
+return view('name');
+```
+
+Where *name* is the name of your view file.
+
+!!! important "Important"
+ If the file extension is omitted, then the views are expected to end with the **.php** extension.
+
+!!! note "Note"
+ The `view()` function uses [View Renderer](view_renderer.md) internally.
+
+Now, create a file called **Blog.php** in the **app/Controllers** directory, and put this in it:
+
+```php
+get('blog', [Blog::class, 'index']);
+```
+
+If you visit your site, you should see your new view. The URL is similar to this:
+
+```
+example.com/index.php/blog/
+```
+
+## Loading Multiple Views
+
+CodeIgniter will intelligently handle multiple calls to `view()` from within a controller. If more than one call happens, they will be appended together.
+
+For example, you may wish to have a header view, a menu view, a content view, and a footer view. That might look something like this:
+
+```php
+ 'Your title',
+ ];
+
+ return view('header')
+ . view('menu')
+ . view('content', $data)
+ . view('footer');
+ }
+}
+```
+
+In the example above, we are using "dynamically added data", which you will see below.
+
+## Storing Views within Sub-directories
+
+Your view files can also be stored within sub-directories if you prefer that type of organization. When doing so, you will need to include the directory name when loading the view. Example:
+
+```php
+return view('directory_name/file_name');
+```
+
+## Namespaced Views
+
+You can store views under a **Views** directory that is namespaced, and load that view as if it were namespaced. While PHP does not support loading non-class files from a namespace, CodeIgniter provides this feature to make it possible to package your views together in a module-like fashion for easy re-use or distribution.
+
+If you have an **example/blog** directory that has a PSR-4 mapping set up in the [Autoloader](../concepts/autoloader.md) living under the namespace `Example\Blog`, you could retrieve view files as if they were namespaced also.
+
+Following this example, you could load the **blog_view.php** file from **example/blog/Views** by prepending the namespace to the view name (specifying the end directory `Views` as in `Example\Blog\Views\` is not required):
+
+```php
+ 60]);
+```
+
+By default, the view will be cached using the same name as the view file itself. You can customize this by passing along `cache_name` and the cache ID you wish to use:
+
+```php
+// Cache the view for 60 seconds
+return view('file_name', $data, ['cache' => 60, 'cache_name' => 'my_cached_view']);
+```
+
+## Adding Dynamic Data to the View
+
+Data is passed from the controller to the view by way of an array in the second parameter of the `view()` function.
+
+Here's an example:
+
+```php
+$data = [
+ 'title' => 'My title',
+ 'heading' => 'My Heading',
+ 'message' => 'My Message',
+];
+
+return view('blog_view', $data);
+```
+
+Let's try it with your controller file. Open it and add this code:
+
+```php
+
+
+ = esc($title) ?>
+
+
+ = esc($heading) ?>
+
+