Skip to content

Commit dce194b

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into 4.5
2 parents 9e6d34f + b367f2f commit dce194b

File tree

15 files changed

+340
-58
lines changed

15 files changed

+340
-58
lines changed

system/HTTP/Exceptions/HTTPException.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,15 @@ public static function forInvalidSameSiteSetting(string $samesite)
230230
{
231231
return new static(lang('Security.invalidSameSiteSetting', [$samesite]));
232232
}
233+
234+
/**
235+
* Thrown when the JSON format is not supported.
236+
* This is specifically for cases where data validation is expected to work with key-value structures.
237+
*
238+
* @return HTTPException
239+
*/
240+
public static function forUnsupportedJSONFormat()
241+
{
242+
return new static(lang('HTTP.unsupportedJSONFormat'));
243+
}
233244
}

system/Helpers/filesystem_helper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ function get_filenames(
211211

212212
try {
213213
foreach (new RecursiveIteratorIterator(
214-
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS),
214+
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS),
215215
RecursiveIteratorIterator::SELF_FIRST
216216
) as $name => $object) {
217217
$basename = pathinfo($name, PATHINFO_BASENAME);

system/Language/en/HTTP.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
// IncomingRequest
2323
'invalidNegotiationType' => '"{0}" is not a valid negotiation type. Must be one of: media, charset, encoding, language.',
2424
'invalidJSON' => 'Failed to parse JSON string. Error: {0}',
25+
'unsupportedJSONFormat' => 'The provided JSON format is not supported.',
2526

2627
// Message
2728
'invalidHTTPProtocol' => 'Invalid HTTP Protocol Version: {0}',

system/Model.php

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,21 @@ protected function doErrors()
519519
public function getIdValue($data)
520520
{
521521
if (is_object($data) && isset($data->{$this->primaryKey})) {
522+
// Get the raw primary key value of the Entity.
523+
if ($data instanceof Entity) {
524+
$cast = $data->cast();
525+
526+
// Disable Entity casting, because raw primary key value is needed for database.
527+
$data->cast(false);
528+
529+
$primaryKey = $data->{$this->primaryKey};
530+
531+
// Restore Entity casting setting.
532+
$data->cast($cast);
533+
534+
return $primaryKey;
535+
}
536+
522537
return $data->{$this->primaryKey};
523538
}
524539

@@ -781,37 +796,7 @@ public function update($id = null, $data = null): bool
781796
*/
782797
protected function objectToRawArray($data, bool $onlyChanged = true, bool $recursive = false): array
783798
{
784-
$properties = parent::objectToRawArray($data, $onlyChanged);
785-
786-
$primaryKey = null;
787-
788-
if ($data instanceof Entity) {
789-
$cast = $data->cast();
790-
791-
// Disable Entity casting, because raw primary key data is needed for database.
792-
$data->cast(false);
793-
794-
$primaryKey = $data->{$this->primaryKey};
795-
796-
// Restore Entity casting setting.
797-
$data->cast($cast);
798-
}
799-
800-
// Always grab the primary key otherwise updates will fail.
801-
if (
802-
// @TODO Should use `$data instanceof Entity`.
803-
method_exists($data, 'toRawArray')
804-
&& (
805-
! empty($properties)
806-
&& ! empty($this->primaryKey)
807-
&& ! in_array($this->primaryKey, $properties, true)
808-
&& ! empty($primaryKey)
809-
)
810-
) {
811-
$properties[$this->primaryKey] = $primaryKey;
812-
}
813-
814-
return $properties;
799+
return parent::objectToRawArray($data, $onlyChanged);
815800
}
816801

817802
/**

system/Validation/Validation.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace CodeIgniter\Validation;
1515

1616
use Closure;
17+
use CodeIgniter\HTTP\Exceptions\HTTPException;
1718
use CodeIgniter\HTTP\IncomingRequest;
1819
use CodeIgniter\HTTP\Method;
1920
use CodeIgniter\HTTP\RequestInterface;
@@ -507,6 +508,10 @@ public function withRequest(RequestInterface $request): ValidationInterface
507508
if (strpos($request->getHeaderLine('Content-Type'), 'application/json') !== false) {
508509
$this->data = $request->getJSON(true);
509510

511+
if (! is_array($this->data)) {
512+
throw HTTPException::forUnsupportedJSONFormat();
513+
}
514+
510515
return $this;
511516
}
512517

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace Tests\Support\Entity\Cast;
13+
14+
use CodeIgniter\Entity\Cast\BaseCast;
15+
16+
class CastBinaryUUID extends BaseCast
17+
{
18+
/**
19+
* Get
20+
*
21+
* @param string $binary Binary UUID
22+
*
23+
* @return string String UUID
24+
*/
25+
public static function get($binary, array $params = []): string
26+
{
27+
$string = unpack('h*', $binary);
28+
29+
return preg_replace('/([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})/', '$1-$2-$3-$4-$5', $string[1]);
30+
}
31+
32+
/**
33+
* Set
34+
*
35+
* @param string $string String UUID
36+
*
37+
* @return string Binary UUID
38+
*/
39+
public static function set($string, array $params = []): string
40+
{
41+
return pack('h*', str_replace('-', '', $string));
42+
}
43+
}

tests/_support/Entity/UUID.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace Tests\Support\Entity;
13+
14+
use CodeIgniter\Entity\Entity;
15+
use Tests\Support\Entity\Cast\CastBinaryUUID;
16+
17+
class UUID extends Entity
18+
{
19+
protected $casts = [
20+
'id' => 'uuid',
21+
];
22+
protected $castHandlers = [
23+
'uuid' => CastBinaryUUID::class,
24+
];
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace Tests\Support\Models;
13+
14+
use CodeIgniter\Model;
15+
use Tests\Support\Entity\UUID;
16+
17+
class UUIDPkeyModel extends Model
18+
{
19+
protected $table = 'uuid';
20+
protected $useAutoIncrement = false;
21+
protected $returnType = UUID::class;
22+
protected $allowedFields = [
23+
'value',
24+
];
25+
}

tests/system/Helpers/FilesystemHelperTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,35 @@ public function testGetFilenamesFailure(): void
398398
$this->assertSame([], get_filenames(SUPPORTPATH . 'Files/shaker/'));
399399
}
400400

401+
public function testGetFilenamesWithSymlinks(): void
402+
{
403+
$targetDir = APPPATH . 'Language';
404+
$linkDir = APPPATH . 'Controllers/Language';
405+
if (file_exists($linkDir)) {
406+
unlink($linkDir);
407+
}
408+
symlink($targetDir, $linkDir);
409+
410+
$targetFile = APPPATH . 'Common.php';
411+
$linkFile = APPPATH . 'Controllers/Common.php';
412+
if (file_exists($linkFile)) {
413+
unlink($linkFile);
414+
}
415+
symlink($targetFile, $linkFile);
416+
417+
$this->assertSame([
418+
0 => 'BaseController.php',
419+
1 => 'Common.php',
420+
2 => 'Home.php',
421+
3 => 'Language',
422+
4 => 'Validation.php',
423+
5 => 'en',
424+
], get_filenames(APPPATH . 'Controllers'));
425+
426+
unlink($linkDir);
427+
unlink($linkFile);
428+
}
429+
401430
public function testGetDirFileInfo(): void
402431
{
403432
$file = SUPPORTPATH . 'Files/baker/banana.php';

tests/system/Models/UpdateModelTest.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
use CodeIgniter\Database\Exceptions\DatabaseException;
1717
use CodeIgniter\Database\Exceptions\DataException;
1818
use CodeIgniter\Entity\Entity;
19+
use Config\Database;
1920
use InvalidArgumentException;
2021
use stdClass;
22+
use Tests\Support\Entity\UUID;
2123
use Tests\Support\Models\EventModel;
2224
use Tests\Support\Models\JobModel;
2325
use Tests\Support\Models\SecondaryModel;
2426
use Tests\Support\Models\UserModel;
27+
use Tests\Support\Models\UUIDPkeyModel;
2528
use Tests\Support\Models\ValidModel;
2629
use Tests\Support\Models\WithoutAutoIncrementModel;
2730

@@ -377,6 +380,98 @@ public function testUpdateWithEntityNoAllowedFields(): void
377380
$this->model->update($id, $entity);
378381
}
379382

383+
public function testUpdateEntityWithPrimaryKeyCast(): void
384+
{
385+
if (
386+
$this->db->DBDriver === 'OCI8'
387+
|| $this->db->DBDriver === 'Postgre'
388+
|| $this->db->DBDriver === 'SQLSRV'
389+
|| $this->db->DBDriver === 'SQLite3'
390+
) {
391+
$this->markTestSkipped($this->db->DBDriver . ' does not work with binary data as string data.');
392+
}
393+
394+
$this->createUuidTable();
395+
396+
$this->createModel(UUIDPkeyModel::class);
397+
398+
$entity = new UUID();
399+
$entity->id = '550e8400-e29b-41d4-a716-446655440000';
400+
$entity->value = 'test1';
401+
402+
$id = $this->model->insert($entity);
403+
$entity = $this->model->find($id);
404+
405+
$entity->value = 'id';
406+
$result = $this->model->save($entity);
407+
408+
$this->assertTrue($result);
409+
410+
$entity = $this->model->find($id);
411+
412+
$this->assertSame('id', $entity->value);
413+
}
414+
415+
public function testUpdateBatchEntityWithPrimaryKeyCast(): void
416+
{
417+
if (
418+
$this->db->DBDriver === 'OCI8'
419+
|| $this->db->DBDriver === 'Postgre'
420+
|| $this->db->DBDriver === 'SQLSRV'
421+
|| $this->db->DBDriver === 'SQLite3'
422+
) {
423+
$this->markTestSkipped($this->db->DBDriver . ' does not work with binary data as string data.');
424+
}
425+
426+
// See https://github.com/codeigniter4/CodeIgniter4/pull/8282#issuecomment-1836974182
427+
$this->markTestSkipped(
428+
'batchUpdate() is currently not working due to data type issues in the generated SQL statement.'
429+
);
430+
431+
$this->createUuidTable();
432+
433+
$this->createModel(UUIDPkeyModel::class);
434+
435+
$entity1 = new UUID();
436+
$entity1->id = '550e8400-e29b-41d4-a716-446655440000';
437+
$entity1->value = 'test1';
438+
$id1 = $this->model->insert($entity1);
439+
440+
$entity2 = new UUID();
441+
$entity2->id = 'bd59cff1-7a24-dde5-ac10-7b929db6da8c';
442+
$entity2->value = 'test2';
443+
$id2 = $this->model->insert($entity2);
444+
445+
$entity1 = $this->model->find($id1);
446+
$entity2 = $this->model->find($id2);
447+
448+
$entity1->value = 'update1';
449+
$entity2->value = 'update2';
450+
451+
$data = [
452+
$entity1,
453+
$entity2,
454+
];
455+
$this->model->updateBatch($data, 'id');
456+
457+
$this->seeInDatabase('uuid', [
458+
'value' => 'update1',
459+
]);
460+
$this->seeInDatabase('uuid', [
461+
'value' => 'update2',
462+
]);
463+
}
464+
465+
private function createUuidTable(): void
466+
{
467+
$forge = Database::forge($this->DBGroup);
468+
$forge->dropTable('uuid', true);
469+
$forge->addField([
470+
'id' => ['type' => 'BINARY', 'constraint' => 16],
471+
'value' => ['type' => 'VARCHAR', 'constraint' => 400, 'null' => true],
472+
])->addKey('id', true)->createTable('uuid', true);
473+
}
474+
380475
public function testUseAutoIncrementSetToFalseUpdate(): void
381476
{
382477
$key = 'key';

0 commit comments

Comments
 (0)