Repository: codeigniter4/CodeIgniter4 (PHP)

Issue:
Bug: Model cannot insertBatch when $useAutoIncrement is false
### PHP Version

8.2

### CodeIgniter4 Version

v4.3.3

### CodeIgniter4 Installation Method

Composer (using `codeigniter4/appstarter`)

### Which operating systems have you tested for this bug?

Windows

### Which server did you use?

cli-server (PHP built-in webserver)

### Database

_No response_

### What happened?

Model cannot insert using `insertBatch` when `$useAutoIncrement` is set to `false`.

It's exactly the same as this issue:
https://forum.codeigniter.com/showthread.php?tid=84733

But it occurs in `insertBatch`.

### Steps to Reproduce

1. Create a model, set `$useAutoIncrement` to `false`, and exclude `$primaryKey` from `$allowedFields` as mentioned in the [docs](https://codeigniter.com/user_guide/models/model.html#allowedfields).

2. Instantiate model, `$model = model(MyModel::class);`

3. Utilize the `$model->insertBatch($data);` method to insert data, including the primary key.

### Expected Output

The data should be inserted into the database.

### Anything else?

https://forum.codeigniter.com/showthread.php?tid=84733


Discussion:
I've confirmed this bug.

> CodeIgniter\Database\Exceptions\DataException : There is no primary key defined when trying to make insertBatch.

```diff
--- a/tests/system/Models/InsertModelTest.php
+++ b/tests/system/Models/InsertModelTest.php
@@ -65,6 +65,26 @@ final class InsertModelTest extends LiveModelTestCase
         $this->seeInDatabase('job', ['name' => 'Cab Driver']);
     }
 
+    public function testInsertBatchUseAutoIncrementSetToFalse(): void
+    {
+        $insertData = [
+            [
+                'key'   => 'key1',
+                'value' => 'value1',
+            ],
+            [
+                'key'   => 'key2',
+                'value' => 'value2',
+            ],
+        ];
+
+        $this->createModel(WithoutAutoIncrementModel::class);
+        $this->model->insertBatch($insertData);
+
+        $this->seeInDatabase('without_auto_increment', ['key' => 'key1']);
+        $this->seeInDatabase('without_auto_increment', ['key' => 'key2']);
+    }
+
     public function testInsertBatchValidationFail(): void
     {
         $jobData = [
```

@MaulanaMalikS I sent #7759. Try it if you can.

@kenjis, yes, I've tried it and it works!



Patch:
diff --git a/system/BaseModel.php b/system/BaseModel.php
index 0f316e6ffa3f..ee78e20faacb 100644
--- a/system/BaseModel.php
+++ b/system/BaseModel.php
@@ -847,7 +847,7 @@ public function insertBatch(?array $set = null, ?bool $escape = null, int $batch
 
                 // Must be called first so we don't
                 // strip out created_at values.
-                $row = $this->doProtectFields($row);
+                $row = $this->doProtectFieldsForInsert($row);
 
                 // Set created_at and updated_at with same time
                 $date = $this->setDate();
@@ -1222,10 +1222,10 @@ public function protect(bool $protect = true)
     }
 
     /**
-     * Ensures that only the fields that are allowed to be updated
-     * are in the data array.
+     * Ensures that only the fields that are allowed to be updated are
+     * in the data array.
      *
-     * Used by insert() and update() to protect against mass assignment
+     * Used by update() and updateBatch() to protect against mass assignment
      * vulnerabilities.
      *
      * @param array $data Data
@@ -1251,6 +1251,22 @@ protected function doProtectFields(array $data): array
         return $data;
     }
 
+    /**
+     * Ensures that only the fields that are allowed to be inserted are in
+     * the data array.
+     *
+     * Used by insert() and insertBatch() to protect against mass assignment
+     * vulnerabilities.
+     *
+     * @param array $data Data
+     *
+     * @throws DataException
+     */
+    protected function doProtectFieldsForInsert(array $data): array
+    {
+        return $this->doProtectFields($data);
+    }
+
     /**
      * Sets the date or current date if null value is passed.
      *
diff --git a/system/Model.php b/system/Model.php
index a9c8e4f2eba9..86bff56421ab 100644
--- a/system/Model.php
+++ b/system/Model.php
@@ -730,6 +730,41 @@ public function insert($data = null, bool $returnID = true)
         return parent::insert($data, $returnID);
     }
 
+    /**
+     * Ensures that only the fields that are allowed to be inserted are in
+     * the data array.
+     *
+     * Used by insert() and insertBatch() to protect against mass assignment
+     * vulnerabilities.
+     *
+     * @param array $data Data
+     *
+     * @throws DataException
+     */
+    protected function doProtectFieldsForInsert(array $data): array
+    {
+        if (! $this->protectFields) {
+            return $data;
+        }
+
+        if (empty($this->allowedFields)) {
+            throw DataException::forInvalidAllowedFields(static::class);
+        }
+
+        foreach (array_keys($data) as $key) {
+            // Do not remove the non-auto-incrementing primary key data.
+            if ($this->useAutoIncrement === false && $key === $this->primaryKey) {
+                continue;
+            }
+
+            if (! in_array($key, $this->allowedFields, true)) {
+                unset($data[$key]);
+            }
+        }
+
+        return $data;
+    }
+
     /**
      * Updates a single record in the database. If an object is provided,
      * it will attempt to convert it into an array.


Test patch:
diff --git a/tests/system/Models/InsertModelTest.php b/tests/system/Models/InsertModelTest.php
index 141eebdd6307..f538b417c935 100644
--- a/tests/system/Models/InsertModelTest.php
+++ b/tests/system/Models/InsertModelTest.php
@@ -65,6 +65,26 @@ public function testInsertBatchSuccess(): void
         $this->seeInDatabase('job', ['name' => 'Cab Driver']);
     }
 
+    public function testInsertBatchUseAutoIncrementSetToFalse(): void
+    {
+        $insertData = [
+            [
+                'key'   => 'key1',
+                'value' => 'value1',
+            ],
+            [
+                'key'   => 'key2',
+                'value' => 'value2',
+            ],
+        ];
+
+        $this->createModel(WithoutAutoIncrementModel::class);
+        $this->model->insertBatch($insertData);
+
+        $this->seeInDatabase('without_auto_increment', ['key' => 'key1']);
+        $this->seeInDatabase('without_auto_increment', ['key' => 'key2']);
+    }
+
     public function testInsertBatchValidationFail(): void
     {
         $jobData = [