Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/deploy-apidocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
tools: phive
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-userguide-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
coverage: none
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reusable-coveralls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ inputs.php-version }}
tools: composer
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reusable-phpunit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ inputs.php-version }}
tools: composer
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reusable-serviceless-phpunit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
fetch-depth: 0

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ inputs.php-version }}
tools: composer
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-autoreview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-coding-standards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php-version }}
extensions: tokenizer
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-phpstan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: '8.2'
extensions: intl
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-psalm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php-version }}
extensions: intl, json, mbstring, xml, mysqli, oci8, pgsql, sqlsrv, sqlite3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-random-execution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ jobs:
fetch-depth: 0

- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php-version }}
extensions: gd, curl, iconv, json, mbstring, openssl, sodium
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-rector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php-version }}
extensions: intl
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-structarmed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup PHP
uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # 2.37.1
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php-version }}
extensions: intl
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"psr/log": "^3.0"
},
"require-dev": {
"boundwize/structarmed": "0.12.0",
"boundwize/structarmed": "0.12.6",
"codeigniter/phpstan-codeigniter": "^1.5",
"fakerphp/faker": "^1.24",
"kint-php/kint": "^6.1",
Expand Down
46 changes: 32 additions & 14 deletions system/API/BaseTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
*/
abstract class BaseTransformer implements TransformerInterface
{
/**
* Nesting depth of the transformation currently in progress (0 = root).
*/
private static int $depth = 0;

/**
* @var list<string>|null
*/
Expand All @@ -69,17 +74,24 @@ abstract class BaseTransformer implements TransformerInterface
public function __construct(
private ?IncomingRequest $request = null,
) {
// An explicitly provided request is always honored - its scope is
// intentional. Only the implicit global fallback is suppressed for
// nested transformers, which is the actual leak vector.
$explicitRequest = $request instanceof IncomingRequest;

$this->request = $request ?? request();

$fields = $this->request->getGet('fields');
$this->fields = is_string($fields)
? array_map(trim(...), explode(',', $fields))
: $fields;
if ($explicitRequest || self::$depth === 0) {
$fields = $this->request->getGet('fields');
$this->fields = is_string($fields)
? array_map(trim(...), explode(',', $fields))
: $fields;

$includes = $this->request->getGet('include');
$this->includes = is_string($includes)
? array_map(trim(...), explode(',', $includes))
: $includes;
$includes = $this->request->getGet('include');
$this->includes = is_string($includes)
? array_map(trim(...), explode(',', $includes))
: $includes;
}
}

/**
Expand Down Expand Up @@ -207,13 +219,19 @@ private function insertIncludes(array $data): array
}
}

foreach ($this->includes as $include) {
$method = 'include' . ucfirst($include);
if (method_exists($this, $method)) {
$data[$include] = $this->{$method}();
} else {
throw ApiException::forMissingInclude($include);
self::$depth++;

try {
foreach ($this->includes as $include) {
$method = 'include' . ucfirst($include);
if (method_exists($this, $method)) {
$data[$include] = $this->{$method}();
} else {
throw ApiException::forMissingInclude($include);
}
}
} finally {
self::$depth--;
}

return $data;
Expand Down
10 changes: 5 additions & 5 deletions system/Database/BaseResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ public function getCustomRowObject(int $n, string $className)
$this->currentRow = $n;
}

return $this->customResultObject[$className][$this->currentRow];
return $this->customResultObject[$className][$this->currentRow] ?? null;
}

/**
Expand All @@ -333,7 +333,7 @@ public function getRowArray(int $n = 0)
$this->currentRow = $n;
}

return $result[$this->currentRow];
return $result[$this->currentRow] ?? null;
}

/**
Expand All @@ -350,11 +350,11 @@ public function getRowObject(int $n = 0)
return null;
}

if ($n !== $this->customResultObject && isset($result[$n])) {
if ($n !== $this->currentRow && isset($result[$n])) {
$this->currentRow = $n;
}

return $result[$this->currentRow];
return $result[$this->currentRow] ?? null;
}

/**
Expand Down Expand Up @@ -440,7 +440,7 @@ public function getPreviousRow(string $type = 'object')
$this->currentRow--;
}

return $result[$this->currentRow];
return $result[$this->currentRow] ?? null;
}

/**
Expand Down
26 changes: 16 additions & 10 deletions system/Session/Handlers/FileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,17 +240,19 @@ public function close(): bool
*/
public function destroy($id): bool
{
$filePath = $this->filePath . $id;

if ($this->close()) {
return is_file($this->filePath . $id)
? (unlink($this->filePath . $id) && $this->destroyCookie())
return (! is_link($filePath) && is_file($filePath))
? (@unlink($filePath) && $this->destroyCookie())
: true;
}

if ($this->filePath !== null) {
clearstatcache();

return is_file($this->filePath . $id)
? (unlink($this->filePath . $id) && $this->destroyCookie())
return (! is_link($filePath) && is_file($filePath))
? (@unlink($filePath) && $this->destroyCookie())
: true;
}

Expand Down Expand Up @@ -284,15 +286,19 @@ public function gc($max_lifetime): false|int

while (($file = readdir($directory)) !== false) {
// If the filename doesn't match this pattern, it's either not a session file or is not ours
if (preg_match($pattern, $file) !== 1
|| ! is_file($this->savePath . DIRECTORY_SEPARATOR . $file)
|| ($mtime = filemtime($this->savePath . DIRECTORY_SEPARATOR . $file)) === false
|| $mtime > $ts
) {
if (preg_match($pattern, $file) !== 1) {
continue;
}

$filePath = $this->savePath . DIRECTORY_SEPARATOR . $file;

$stat = @lstat($filePath);

if ($stat === false || is_link($filePath) || ! is_file($filePath) || $stat['mtime'] > $ts) {
Comment on lines +295 to +297

@michalsn michalsn Jun 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once is_link() filters links out, filemtime() on a non-link equals the stat['mtime'] - the whole lstat() call is unnecessary.

continue;
}

unlink($this->savePath . DIRECTORY_SEPARATOR . $file);
@unlink($filePath);
$collected++;
}

Expand Down
31 changes: 31 additions & 0 deletions tests/_support/API/ChildTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Tests\Support\API;

use CodeIgniter\API\BaseTransformer;

/**
* Nested transformer used to verify that the root request's scope
* (fields/includes) does not leak into related resources.
*/
class ChildTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'child_id' => $resource['id'] ?? null,
'status' => 'transformed',
];
}
}
67 changes: 67 additions & 0 deletions tests/_support/API/ParentTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace Tests\Support\API;

use CodeIgniter\API\BaseTransformer;

/**
* Root transformer used to verify that nested transformers do not inherit
* the root request's `fields`/`include` query state.
*/
class ParentTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'parent_id' => $resource['id'] ?? null,
];
}

/**
* Includes a single related child resource.
*
* @return array<string, mixed>
*/
protected function includeChildren(): array
{
return (new ChildTransformer())->transform(['id' => 99]);
}

/**
* Includes a collection of related child resources.
*
* @return list<array<string, mixed>>
*/
protected function includeChildrenCollection(): array
{
return (new ChildTransformer())->transformMany([
['id' => 77],
['id' => 88],
]);
}

/**
* Includes a single child while explicitly forwarding the request, opting
* the child into the request-derived scope even though it is nested.
*
* @return array<string, mixed>
*/
protected function includeExplicitChild(): array
{
$childRequest = clone request();
$childRequest->setGlobal('get', ['fields' => 'child_id']);

return (new ChildTransformer($childRequest))->transform(['id' => 99]);
}
}
Loading
Loading