Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion system/BaseModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ abstract class BaseModel
*/
protected $allowedFields = [];

/**
* Fields that may be inserted but not updated through Model write methods.
*
* @var list<string>
*/
protected array $insertOnlyFields = [];

/**
* If true, will set created_at, and updated_at
* values during insert and update routines.
Expand Down Expand Up @@ -1162,7 +1169,10 @@ public function updateBatch(?array $set = null, ?string $index = null, int $batc

// Must be called first so we don't
// strip out updated_at values.
$this->ensureNoDisallowedFields($row, $index === null ? [] : [$index]);
$ignoredFields = $index === null ? [] : [$index];

$row = $this->doProtectInsertOnlyFieldsForUpdate($row, $ignoredFields);
$this->ensureNoDisallowedFields($row, $ignoredFields);
$row = $this->doProtectFields($row);

// Restore updateIndex value in case it was wiped out
Expand Down Expand Up @@ -1375,6 +1385,20 @@ public function setAllowedFields(array $allowedFields)
return $this;
}

/**
* Sets the fields that may be inserted but not updated.
*
* @param list<string> $insertOnlyFields
*
* @return $this
*/
public function setInsertOnlyFields(array $insertOnlyFields)
{
$this->insertOnlyFields = $insertOnlyFields;

return $this;
}

/**
* Sets whether or not we should whitelist data set during
* updates or inserts against $this->availableFields.
Expand Down Expand Up @@ -1462,6 +1486,42 @@ protected function ensureNoDisallowedFields(array $row, array $ignoredFields = [
}
}

/**
* Removes fields from update data when they may only be inserted.
*
* @param row_array $row
* @param list<string> $ignoredFields
*
* @return row_array
*
* @throws DataException
*/
protected function doProtectInsertOnlyFieldsForUpdate(array $row, array $ignoredFields = []): array
{
if (! $this->protectFields || $this->allowedFields === [] || $this->insertOnlyFields === []) {
return $row;
}

$insertOnlyFields = [];

foreach (array_keys($row) as $key) {
if (in_array($key, $ignoredFields, true)) {
continue;
}

if (in_array($key, $this->insertOnlyFields, true)) {
$insertOnlyFields[] = $key;
unset($row[$key]);
}
}

if ($insertOnlyFields !== [] && $this->throwOnDisallowedFields) {
throw DataException::forInsertOnlyFields(static::class, $insertOnlyFields);
}

return $row;
}

/**
* Ensures that only the fields that are allowed to be inserted are in
* the data array.
Expand Down Expand Up @@ -1496,6 +1556,7 @@ protected function doProtectFieldsForInsert(array $row): array
*/
protected function doProtectFieldsForUpdate(array $row): array
{
$row = $this->doProtectInsertOnlyFieldsForUpdate($row);
$this->ensureNoDisallowedFields($row);

return $this->doProtectFields($row);
Expand Down
17 changes: 9 additions & 8 deletions system/Commands/Generators/Views/model.tpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
class {class} extends Model
{
<?php if (is_string($dbGroup)): ?>
protected $DBGroup = '{dbGroup}';
protected $DBGroup = '{dbGroup}';
<?php endif; ?>
protected $table = '{table}';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $returnType = {return};
protected $useSoftDeletes = false;
protected $protectFields = true;
protected $allowedFields = [];
protected $table = '{table}';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $returnType = {return};
protected $useSoftDeletes = false;
protected $protectFields = true;
protected $allowedFields = [];
protected array $insertOnlyFields = [];

protected bool $throwOnDisallowedFields = false;
protected bool $allowEmptyInserts = false;
Expand Down
10 changes: 10 additions & 0 deletions system/Database/Exceptions/DataException.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ public static function forDisallowedFields(string $model, array $fields)
return new static(lang('Database.disallowedFields', [$model, implode(', ', $fields)]));
}

/**
* @param list<string> $fields
*
* @return DataException
*/
public static function forInsertOnlyFields(string $model, array $fields)
{
return new static(lang('Database.insertOnlyFields', [$model, implode(', ', $fields)]));
}

/**
* @return DataException
*/
Expand Down
1 change: 1 addition & 0 deletions system/Language/en/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
'invalidArgument' => 'You must provide a valid "{0}".',
'invalidAllowedFields' => 'Allowed fields must be specified for model: "{0}"',
'disallowedFields' => 'Fields are not allowed for model "{0}": {1}',
'insertOnlyFields' => 'Fields cannot be updated for model "{0}": {1}',
'emptyDataset' => 'There is no data to {0}.',
'emptyPrimaryKey' => 'There is no primary key defined when trying to make {0}.',
'failGetFieldData' => 'Failed to get field data from database.',
Expand Down
1 change: 1 addition & 0 deletions system/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ protected function doProtectFieldsForInsert(array $row): array

protected function doProtectFieldsForUpdate(array $row): array
{
$row = $this->doProtectInsertOnlyFieldsForUpdate($row, [$this->primaryKey]);
$this->ensureNoDisallowedFields($row, [$this->primaryKey]);

return $this->doProtectFields($row);
Expand Down
14 changes: 7 additions & 7 deletions tests/system/Commands/Generators/ModelGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public function testGenerateModel(): void
$file = APPPATH . 'Models/User.php';
$this->assertFileExists($file);
$this->assertStringContainsString('extends Model', $this->getFileContent($file));
$this->assertStringContainsString('protected $table = \'users\';', $this->getFileContent($file));
$this->assertStringContainsString('protected $returnType = \'array\';', $this->getFileContent($file));
$this->assertStringContainsString('protected $table = \'users\';', $this->getFileContent($file));
$this->assertStringContainsString('protected $returnType = \'array\';', $this->getFileContent($file));
}

public function testGenerateModelWithOptionTable(): void
Expand All @@ -63,7 +63,7 @@ public function testGenerateModelWithOptionTable(): void
$this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer());
$file = APPPATH . 'Models/Cars.php';
$this->assertFileExists($file);
$this->assertStringContainsString('protected $table = \'utilisateur\';', $this->getFileContent($file));
$this->assertStringContainsString('protected $table = \'utilisateur\';', $this->getFileContent($file));
}

public function testGenerateModelWithOptionDBGroup(): void
Expand All @@ -72,7 +72,7 @@ public function testGenerateModelWithOptionDBGroup(): void
$this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer());
$file = APPPATH . 'Models/User.php';
$this->assertFileExists($file);
$this->assertStringContainsString('protected $DBGroup = \'testing\';', $this->getFileContent($file));
$this->assertStringContainsString('protected $DBGroup = \'testing\';', $this->getFileContent($file));
}

public function testGenerateModelWithOptionReturnArray(): void
Expand All @@ -81,7 +81,7 @@ public function testGenerateModelWithOptionReturnArray(): void
$this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer());
$file = APPPATH . 'Models/User.php';
$this->assertFileExists($file);
$this->assertStringContainsString('protected $returnType = \'array\';', $this->getFileContent($file));
$this->assertStringContainsString('protected $returnType = \'array\';', $this->getFileContent($file));
}

public function testGenerateModelWithOptionReturnObject(): void
Expand All @@ -90,7 +90,7 @@ public function testGenerateModelWithOptionReturnObject(): void
$this->assertStringContainsString('File created: ', $this->getStreamFilterBuffer());
$file = APPPATH . 'Models/User.php';
$this->assertFileExists($file);
$this->assertStringContainsString('protected $returnType = \'object\';', $this->getFileContent($file));
$this->assertStringContainsString('protected $returnType = \'object\';', $this->getFileContent($file));
}

public function testGenerateModelWithOptionReturnEntity(): void
Expand All @@ -100,7 +100,7 @@ public function testGenerateModelWithOptionReturnEntity(): void

$file = APPPATH . 'Models/User.php';
$this->assertFileExists($file);
$this->assertStringContainsString('protected $returnType = \App\Entities\User::class;', $this->getFileContent($file));
$this->assertStringContainsString('protected $returnType = \App\Entities\User::class;', $this->getFileContent($file));

if (is_file($file)) {
unlink($file);
Expand Down
Loading
Loading