Skip to content

Commit ff60c9a

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into 4.8
2 parents 678db72 + 2bd0f01 commit ff60c9a

14 files changed

Lines changed: 169 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,81 @@
11
# Changelog
22

3+
## [v4.7.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.4) (2026-07-07)
4+
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.7.3...v4.7.4)
5+
6+
### Security
7+
8+
* **IncomingRequest:** *HTTPS detection via client-supplied headers* was fixed.
9+
``IncomingRequest::isSecure()`` now trusts the ``X-Forwarded-Proto`` and
10+
``Front-End-Https`` headers only when the request comes from a trusted proxy
11+
configured in ``Config\App::$proxyIPs``.
12+
See the `Security advisory GHSA-7wmf-pw8j-mc78 <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-7wmf-pw8j-mc78>`_
13+
for more information.
14+
15+
* **Query Builder:** Fixed a SQL injection vulnerability in ``deleteBatch()``.
16+
When ``deleteBatch()`` was used together with ``where()`` conditions, the
17+
bound values from the WHERE clause were substituted into the generated SQL
18+
with their escape flag ignored, so they were never escaped or quoted. The
19+
WHERE binds are now escaped in the same way as a regular ``delete()``.
20+
21+
See the `Security advisory GHSA-c9w5-rwh3-7pm9 <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-c9w5-rwh3-7pm9>`_
22+
for more information.
23+
24+
* **UploadedFile:** ``UploadedFile::move()`` now sanitizes the client-provided
25+
filename when called without a second argument. Previously, the unsanitized
26+
client filename was used as the default, allowing path traversal sequences
27+
(e.g. ``../../public/shell.php``) to write the uploaded file outside the
28+
intended directory. A name explicitly passed as the second argument is not
29+
sanitized and remains the caller's responsibility.
30+
See the `Security advisory GHSA-hhmc-q9hp-r662 <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-hhmc-q9hp-r662>`_
31+
for more information.
32+
33+
* **Validation:** The ``is_image`` and ``mime_in`` file upload validation rules
34+
now also verify non-empty client filename extensions. Previously, these rules
35+
classified an upload solely by its content-derived MIME type, so a file with a
36+
dangerous client extension (for example, a ``.php`` file prepended with image
37+
magic bytes) could pass validation while keeping its original extension on
38+
disk.
39+
See the `Security advisory GHSA-mmj4-63m4-r6h5 <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-mmj4-63m4-r6h5>`_
40+
for more information.
41+
42+
The ``is_image`` rule now rejects uploads when a non-empty client filename
43+
extension is not an image extension. The ``mime_in`` rule now rejects uploads
44+
when a non-empty client filename extension does not match the detected file
45+
content, matching the same extension/content agreement used by ``ext_in``.
46+
Files uploaded without any extension (such as JavaScript ``Blob`` uploads)
47+
are still accepted, since the rules validate the file content.
48+
49+
### Fixed Bugs
50+
51+
* fix: prevent updateBatch with existing where conditions by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10236
52+
* fix: detect Safari version from Version token by @memleakd in https://github.com/codeigniter4/CodeIgniter4/pull/10251
53+
* fix: nested transformer request scope leakage by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10281
54+
* fix(model): pass $recursive parameter to parent in objectToRawArray by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10258
55+
* fix: preserve enclosing stream filter when using `MockInputOutput` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10307
56+
* fix: skip already-translated keys in `spark lang:find` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10308
57+
* fix(Filters): check both keys and values in `InvalidChars` arrays by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10303
58+
* fix: use dynamic lockMaxRetries limit in RedisHandler by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10295
59+
* fix: preserve sub-namespace when generating Entity from Model (i.e., `--return entity`) by @xgrind in https://github.com/codeigniter4/CodeIgniter4/pull/10232
60+
* fix: `env()` TypeError for non-string `$_SERVER` values + `esc()` fixes by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10305
61+
* fix: unify casing in BaseService::injectMock by @ThomasMeschke in https://github.com/codeigniter4/CodeIgniter4/pull/10316
62+
* fix: normalize SodiumHandler params and padding failures by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10321
63+
* fix(Validation): correct required_without logic and prevent array key warnings by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10328
64+
* fix: handle null ini values in phpini check by @Will-thom in https://github.com/codeigniter4/CodeIgniter4/pull/10233
65+
* fix: preserve zero values in XML export by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10367
66+
* fix: support array indexes in `getPostGet()` and `getGetPost()` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10362
67+
68+
### Refactoring
69+
70+
* refactor: narrow `Image` original dimension types to `int` by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10326
71+
* refactor(HTTP): optimize file path parsing in `download()` method by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10330
72+
* refactor(database): optimize groupGetType by caching it inside BaseBuilder loops by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10340
73+
* refactor: bump to phpstan-codeigniter v2.1 by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10312
74+
* refactor: replace type `mixed` with more specific types by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10345
75+
* refactor: optimize `prepQuotedPrintable()` with hash lookup and int-length tracking by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10344
76+
* refactor: add precise `array` phpdocs for `CLI` by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/10354
77+
* refactor(database): optimize `_processForeignKeys()` string allocations and loop invariants by @gr8man in https://github.com/codeigniter4/CodeIgniter4/pull/10351
78+
379
## [v4.7.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.3) (2026-05-22)
480
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.7.2...v4.7.3)
581

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"phpunit/phpcov": "^9.0.2 || ^10.0",
3030
"phpunit/phpunit": "^10.5.16 || ^11.2",
3131
"predis/predis": "^3.0",
32-
"rector/rector": "2.5.3",
32+
"rector/rector": "2.5.4",
3333
"shipmonk/phpstan-baseline-per-identifier": "^2.0"
3434
},
3535
"replace": {

phpdoc.dist.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<output>api/build/</output>
1111
<cache>api/cache/</cache>
1212
</paths>
13-
<version number="4.7.3">
13+
<version number="4.7.4">
1414
<api format="php">
1515
<source dsn=".">
1616
<path>system</path>

system/Database/BaseUtils.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ public function getXMLFromResult(ResultInterface $query, array $params = []): st
250250
$xml .= $tab . '<' . $element . '>' . $newline;
251251

252252
foreach ($row as $key => $val) {
253-
$val = empty($val) ? '' : xml_convert((string) $val);
253+
$val = ($val === null || $val === '') ? '' : xml_convert((string) $val);
254254

255255
$xml .= $tab . $tab . '<' . $key . '>' . $val . '</' . $key . '>' . $newline;
256256
}

system/HTTP/IncomingRequest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,16 @@ public function getPostGet($index = null, $filter = null, $flags = null)
620620
return array_merge($this->getGet($index, $filter, $flags), $this->getPost($index, $filter, $flags));
621621
}
622622

623+
if (is_array($index)) {
624+
$output = [];
625+
626+
foreach ($index as $key) {
627+
$output[$key] = $this->getPostGet($key, $filter, $flags);
628+
}
629+
630+
return $output;
631+
}
632+
623633
// Use $_POST directly here, since filter_has_var only
624634
// checks the initial POST data, not anything that might
625635
// have been added since.
@@ -643,6 +653,16 @@ public function getGetPost($index = null, $filter = null, $flags = null)
643653
return array_merge($this->getPost($index, $filter, $flags), $this->getGet($index, $filter, $flags));
644654
}
645655

656+
if (is_array($index)) {
657+
$output = [];
658+
659+
foreach ($index as $key) {
660+
$output[$key] = $this->getGetPost($key, $filter, $flags);
661+
}
662+
663+
return $output;
664+
}
665+
646666
// Use $_GET directly here, since filter_has_var only
647667
// checks the initial GET data, not anything that might
648668
// have been added since.

tests/system/Database/Live/DbUtilsTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,24 @@ public function testUtilsXMLFromResult(): void
205205

206206
$this->assertSame($expected, $actual);
207207
}
208+
209+
public function testUtilsXMLFromResultWithZero(): void
210+
{
211+
$this->db->table('job')->insert([
212+
'name' => '0',
213+
'description' => 'Testing zero value',
214+
]);
215+
$data = $this->db->table('job')->where('name', '0')->get();
216+
217+
$util = (new Database())->loadUtils($this->db);
218+
219+
$data = $util->getXMLFromResult($data);
220+
221+
$expected = '<root><element><id>5</id><name>0</name><description>Testing zero value</description><created_at></created_at><updated_at></updated_at><deleted_at></deleted_at></element></root>';
222+
223+
$actual = preg_replace('#\R+#', '', $data);
224+
$actual = preg_replace('/[ ]{2,}|[\t]/', '', $actual);
225+
226+
$this->assertSame($expected, $actual);
227+
}
208228
}

tests/system/HTTP/IncomingRequestTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,34 @@ public function testCanGrabPostBeforeGet(): void
101101
$this->assertSame('3', $this->request->getGetPost('TEST'));
102102
}
103103

104+
public function testCanGrabMultiplePostAndGetVars(): void
105+
{
106+
service('superglobals')
107+
->setPostArray([
108+
'post' => 'post value',
109+
'shared' => 'post shared value',
110+
])
111+
->setGetArray([
112+
'get' => 'get value',
113+
'shared' => 'get shared value',
114+
]);
115+
116+
$index = ['post', 'get', 'shared', 'missing'];
117+
118+
$this->assertSame([
119+
'post' => 'post value',
120+
'get' => 'get value',
121+
'shared' => 'post shared value',
122+
'missing' => null,
123+
], $this->request->getPostGet($index));
124+
$this->assertSame([
125+
'post' => 'post value',
126+
'get' => 'get value',
127+
'shared' => 'get shared value',
128+
'missing' => null,
129+
], $this->request->getGetPost($index));
130+
}
131+
104132
public function testNoOldInput(): void
105133
{
106134
$this->assertNull($this->request->getOldInput('name'));

user_guide_src/source/changelogs/v4.7.4.rst

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Version 4.7.4
33
#############
44

5-
Release Date: Unreleased
5+
Release Date: July 7, 2026
66

77
**4.7.4 release of CodeIgniter4**
88

@@ -55,22 +55,6 @@ SECURITY
5555
Files uploaded without any extension (such as JavaScript ``Blob`` uploads)
5656
are still accepted, since the rules validate the file content.
5757

58-
********
59-
BREAKING
60-
********
61-
62-
***************
63-
Message Changes
64-
***************
65-
66-
*******
67-
Changes
68-
*******
69-
70-
************
71-
Deprecations
72-
************
73-
7458
**********
7559
Bugs Fixed
7660
**********
@@ -82,8 +66,10 @@ Bugs Fixed
8266
- **Commands:** Fixed a bug where ``spark lang:find`` treated translation keys already provided by the framework or another namespace (such as ``Errors.*`` in ``system/Language``) as new, listing them under ``--show-new`` and writing untranslated placeholders into ``app/Language`` that overrode the existing translations.
8367
- **Config:** Fixed a bug where ``BaseService::injectMock`` did not apply ``strtolower`` consistently, causing inconsistent Service and Mock registration and resolution.
8468
- **Database:** Fixed a bug where ``updateBatch()`` could be called after Query Builder ``where()`` conditions, even though it's not supported. In this situation, now the ``DatabaseException`` is thrown.
69+
- **Database:** Fixed a bug in ``BaseUtils::getXMLFromResult()`` where database values of ``0`` or ``'0'`` were treated as empty and omitted from the generated XML export.
8570
- **Encryption:** Fixed bugs in ``SodiumHandler`` where runtime ``blockSize`` overrides without ``key`` were handled incorrectly, invalid key lengths could leak native Sodium errors, and mismatched decrypt ``blockSize`` values could throw ``SodiumException`` instead of ``EncryptionException``.
8671
- **Filters:** Fixed a bug in ``InvalidChars`` filter where invalid UTF-8 or control characters in array keys were not checked.
72+
- **HTTP:** Fixed a bug where ``IncomingRequest::getPostGet()`` and ``IncomingRequest::getGetPost()`` threw a ``TypeError`` when passed an array of indexes.
8773
- **HTTP:** Fixed a bug where the User Agent library reported Safari's WebKit version instead of the browser version from the ``Version`` token.
8874
- **Model:** Fixed a bug in ``Model::objectToRawArray()`` where the ``$recursive`` parameter was ignored.
8975
- **Security:** Fixed a bug where ``CheckPhpIni`` could raise a type error when ``ini_get_all()`` returned ``null`` for a configured directive value.

user_guide_src/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
version = '4.7'
2727

2828
# The full version, including alpha/beta/rc tags.
29-
release = '4.7.3'
29+
release = '4.7.4'
3030

3131
# -- General configuration ---------------------------------------------------
3232

user_guide_src/source/incoming/incomingrequest.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ The methods provided by the parent classes that are available are:
409409

410410
.. php:method:: getGet([$index = null[, $filter = null[, $flags = null]]])
411411
412-
:param string $index: The name of the variable/key to look for.
412+
:param array|string $index: The name of the variable/key to look for, or an array of names.
413413
:param int $filter: The type of filter to apply. A list of filters can be found in
414414
`Types of filters <https://www.php.net/manual/en/filters.php>`__.
415415
:param int $flags: Flags to apply. A list of flags can be found in
@@ -453,7 +453,7 @@ The methods provided by the parent classes that are available are:
453453

454454
.. php:method:: getPost([$index = null[, $filter = null[, $flags = null]]])
455455
456-
:param string $index: The name of the variable/key to look for.
456+
:param array|string $index: The name of the variable/key to look for, or an array of names.
457457
:param int $filter: The type of filter to apply. A list of filters can be
458458
found `here <https://www.php.net/manual/en/filters.php>`__.
459459
:param int $flags: Flags to apply. A list of flags can be found
@@ -465,7 +465,7 @@ The methods provided by the parent classes that are available are:
465465

466466
.. php:method:: getPostGet([$index = null[, $filter = null[, $flags = null]]])
467467
468-
:param string $index: The name of the variable/key to look for.
468+
:param array|string $index: The name of the variable/key to look for, or an array of names.
469469
:param int $filter: The type of filter to apply. A list of filters can be found in
470470
`Types of filters <https://www.php.net/manual/en/filters.php>`__.
471471
:param int $flags: Flags to apply. A list of flags can be found in
@@ -480,12 +480,15 @@ The methods provided by the parent classes that are available are:
480480

481481
.. literalinclude:: incomingrequest/032.php
482482

483+
If an array of indexes is specified, the method returns an associative array and applies
484+
the POST-then-GET lookup order to each index.
485+
483486
If no index is specified, it will return both POST and GET streams combined.
484487
Although POST data will be preferred in case of name conflict.
485488

486489
.. php:method:: getGetPost([$index = null[, $filter = null[, $flags = null]]])
487490
488-
:param string $index: The name of the variable/key to look for.
491+
:param array|string $index: The name of the variable/key to look for, or an array of names.
489492
:param int $filter: The type of filter to apply. A list of filters can be found in
490493
`Types of filters <https://www.php.net/manual/en/filters.php>`__.
491494
:param int $flags: Flags to apply. A list of flags can be found in
@@ -500,6 +503,9 @@ The methods provided by the parent classes that are available are:
500503

501504
.. literalinclude:: incomingrequest/033.php
502505

506+
If an array of indexes is specified, the method returns an associative array and applies
507+
the GET-then-POST lookup order to each index.
508+
503509
If no index is specified, it will return both GET and POST streams combined.
504510
Although GET data will be preferred in case of name conflict.
505511

0 commit comments

Comments
 (0)