Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7d4a53a
[REFACTOR] PHPStan: group legacy-UI custom rules under a LegacyUI nam…
chfsx Jul 7, 2026
11e68ba
[FEATURE] PHPStan: code-rules gate infrastructure
chfsx Jul 7, 2026
ea41024
[FEATURE] PHPStan: forbid writing to request-input superglobals
chfsx Jul 7, 2026
f58dd65
[FIX] Allow SuperGlobal modification in some classes
chfsx Jul 7, 2026
64ae8a7
[FEATURE] PHPStan: grant rule-violation allowances per ILIAS version
chfsx Aug 31, 2026
b920fa6
[FIX] Init: mark the SSO client-id cookie write as a known violation …
chfsx Aug 31, 2026
0902bb7
[FIX] Test: pass the new pool ref id instead of writing to $_REQUEST
chfsx Aug 31, 2026
28f4287
[FIX] WebDAV: mark the request restore in RequestTranslation::close()…
chfsx Aug 31, 2026
b76abc5
[FIX] Form: mark the file input superglobal writes as known violation…
chfsx Aug 31, 2026
e6f2974
[FIX] DataCollection: mark the superglobal writes as known violations…
chfsx Aug 31, 2026
5fbea38
[FIX] TestQuestionPool: mark the file input superglobal writes as kno…
chfsx Aug 31, 2026
d27133f
[FIX] LTIProvider: mark the entry point superglobal writes as known v…
chfsx Aug 31, 2026
cd6c83b
[FIX] CmiXapi: mark the entry point superglobal writes as known viola…
chfsx Aug 31, 2026
ddd53d5
[FIX] Calendar: mark the remote access client-id resolution as a know…
chfsx Aug 31, 2026
424f8f6
[FIX] WebServices: mark the REST entry point client-id write as a kno…
chfsx Aug 31, 2026
20007d6
[FIX] soap: mark the client-id cookie write in ilSoapUserAdministrati…
chfsx Aug 31, 2026
86dbb62
[FIX] PHPStan: keep the code-rules cache out of the repository root
chfsx Aug 31, 2026
7604b04
[FIX] PHPStan: point CI at the moved cache and let the gate block
chfsx Aug 31, 2026
f396ff6
[FEATURE] PHPStan: list the granted rule-violation exemptions
chfsx Aug 31, 2026
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
62 changes: 62 additions & 0 deletions .github/workflows/code-rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: code-rules
on: [pull_request, push]
permissions:
contents: read
pull-requests: read
jobs:
code-rules:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: dom, curl, libxml, mbstring, zip, gd, json, readline, xsl, imagick
tools: composer:v2
coverage: none

- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: vendor/composer/vendor
key: composer-${{ hashFiles('composer.lock') }}
restore-keys: composer-

- name: Prepare Customizing directory
# The composer classmap includes ./public/Customizing/global/plugins; --no-scripts
# skips the pre-install-cmd that would create it, so create it here — otherwise
# autoload generation aborts ("Could not scan for classes inside ...").
run: mkdir -p public/Customizing/global/plugins

- name: Install Composer packages
# --no-scripts skips the post-autoload-dump build (cli/setup.php build), which
# copies front-end assets from node_modules. PHPStan only needs the autoloader
# and classmap (still generated), so neither the build nor Node.js is required.
run: composer install --no-interaction --no-progress --no-scripts

- name: Cache PHPStan result cache
uses: actions/cache@v4
with:
path: scripts/PHPStan/.cache/code-rules
key: phpstan-code-rules-${{ github.run_id }}
restore-keys: phpstan-code-rules-

- name: ILIAS Code Rules
# Hard gate: the branch that introduced these rules also brought the count to
# zero, so any new violation has to be fixed or exempted in the pull request
# that adds it.
run: scripts/PHPStan/run_code_rules.sh
env:
GHRUN: "yes"
ERROR_FORMAT: github

- name: Code Rules Summary
if: always()
run: ERROR_FORMAT=stepSummary scripts/PHPStan/run_code_rules.sh >> "$GITHUB_STEP_SUMMARY" || true
env:
GHRUN: "yes"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/.settings
/errors
/extern
/scripts/PHPStan/.cache
/components/ILIAS/PHPUnit/config/cfg.phpunit.php
/nbproject
/templates/default/delos.css.map
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

use ILIAS\Refinery\Factory as Refinery;
use ILIAS\HTTP\Services as HTTPServices;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* @classDescription Handles requests from external calendar applications
Expand Down Expand Up @@ -49,6 +50,7 @@ public function getTokenHandler(): ?ilCalendarAuthenticationToken
/**
* Fetch client id, the chosen calendar...
*/
#[AllowSuperglobalWrite('resolves the client id for the remote calendar entry point before ilInitialisation runs, so no HTTP service exists yet.', 12)]
public function parseRequest(): void
{
// before initialization: $_GET and $_COOKIE is required is unavoidable
Expand Down
3 changes: 3 additions & 0 deletions components/ILIAS/CmiXapi/classes/XapiProxy/DataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@

namespace XapiProxy;

use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

class DataService
{
#[AllowSuperglobalWrite('the client id has to be visible to ilInitialisation, which runs before the HTTP service exists.', 12)]
public static function initIlias(string $client_id): void
{
define("CLIENT_ID", $client_id);
Expand Down
2 changes: 2 additions & 0 deletions components/ILIAS/CmiXapi/resources/xapitoken.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@
ilCmiXapiAuthToken::OPENSSL_IV
), true);

// @phpstan-ignore ilias.superglobalWrite.v12 (restores session and client id from the signed token before ilInitialisation runs, so no HTTP service exists yet)
$_COOKIE[session_name()] = $param[session_name()];

// @phpstan-ignore ilias.superglobalWrite.v12 (restores session and client id from the signed token before ilInitialisation runs, so no HTTP service exists yet)
$_COOKIE['ilClientId'] = $param['ilClientId'];
$objId = $param['obj_id'];
$refId = $param['ref_id'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

declare(strict_types=1);

use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

class ilDclRecordEditGUI
{
/**
Expand Down Expand Up @@ -486,6 +488,10 @@ public function saveConfirmation(ilDclBaseRecordModel $record_obj, string $fileh
/**
* Save record
*/
#[AllowSuperglobalWrite(
'adds a $_FILES entry for every file input the form expects, because the legacy file inputs read their state from there. The placeholders come from the request, so the shape of the added entries is client-controlled; narrowing this to the field names the form knows is open work.',
12
)]
public function save(): void
{
global $DIC;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

declare(strict_types=1);

use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* Class ilDclPropertyFormGUI
* @ilCtrl_Calls ilDclPropertyFormGUI: ilFormPropertyDispatchGUI
Expand Down Expand Up @@ -71,6 +73,7 @@ public static function getTempFilename(
/**
* @throws ilDclException
*/
#[AllowSuperglobalWrite('re-injects files uploaded in an earlier step of the same form, because the file inputs read them from $_FILES.', 12)]
public static function rebuildTempFileByHash(string $hash): void
{
$temp_path = ilFileUtils::getDataDir() . "/temp";
Expand Down
2 changes: 2 additions & 0 deletions components/ILIAS/Form/classes/class.ilFileInputGUI.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use ILIAS\FileUpload\Exception\IllegalStateException;
use ILIAS\FileUpload\FileUpload;
use ILIAS\UI\Implementation\Component\Input\UploadLimitResolver;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* This class represents a file property in a property form.
Expand Down Expand Up @@ -161,6 +162,7 @@ public function getALlowDeletion(): bool
return $this->allow_deletion;
}

#[AllowSuperglobalWrite('sanitises the uploaded file name in place, because getInput() hands $_FILES[postVar] on to every caller.', 12)]
public function checkInput(): bool
{
if (!$this->upload_service->hasBeenProcessed()) {
Expand Down
2 changes: 2 additions & 0 deletions components/ILIAS/Form/classes/class.ilFileWizardInputGUI.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
declare(strict_types=1);

use ILIAS\Filesystem\Util;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* This class represents a file wizard property in a property form.
Expand Down Expand Up @@ -79,6 +80,7 @@ public function getAllowMove(): bool
return $this->allowMove;
}

#[AllowSuperglobalWrite('sanitises the uploaded file names in place, because the inherited getInput() hands $_FILES[postVar] on to every caller.', 12)]
public function checkInput(): bool
{
$lng = $this->lng;
Expand Down
2 changes: 2 additions & 0 deletions components/ILIAS/Form/classes/class.ilPropertyFormGUI.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

use ILIAS\HTTP;
use ILIAS\Refinery;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* This class represents a property form user interface
Expand Down Expand Up @@ -1012,6 +1013,7 @@ public function moveFileUpload(
return "";
}

#[AllowSuperglobalWrite('re-injects files uploaded in an earlier step of the same form, because the file inputs read them from $_FILES.', 12)]
protected function rebuildUploadedFiles(): void
{
$file_hash = (string) $this->getFileHash();
Expand Down
2 changes: 2 additions & 0 deletions components/ILIAS/Init/classes/class.ilInitialisation.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use ILIAS\User\PublicInterface as UserPublicInterface;
use ILIAS\Mail\Service\MailService;
use ILIAS\Init\AllModernComponents;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

// needed for slow queries, etc.
if (!isset($GLOBALS['ilGlobalStartTime']) || !$GLOBALS['ilGlobalStartTime']) {
Expand All @@ -52,6 +53,7 @@
* @version $Id$
* @ingroup ServicesInit
*/
#[AllowSuperglobalWrite('Remove unsafe Characters, several other legacy mechanisms...', 12)]
class ilInitialisation
{
/**
Expand Down
1 change: 1 addition & 0 deletions components/ILIAS/Init/resources/sso/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
}

setcookie('ilClientId', $_GET['client_id'], 0, $cookie_path, '');
// @phpstan-ignore ilias.superglobalWrite.v12 (the client id has to be visible to ilInitialisation, which runs before the HTTP service exists)
$_COOKIE['ilClientId'] = $_GET['client_id'];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use ILIAS\Init\ErrorHandling\Application\ContextErrorHandlerProvider;
use Whoops\Handler\Handler;
use Whoops\Handler\HandlerInterface;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* A Whoops error handler that delegates calls on it self to another handler that is created only in the
Expand All @@ -34,6 +35,7 @@
* This class is not ment to be extended, as the definition of error handlers should be handled in one place
* in ilErrorHandling, so this class acts rather dump and asks ilErrorHandling for a handler.
*/
#[AllowSuperglobalWrite('The error handler needs to write to SuperGlobals to remove secret data.', 12)]
final class DelegatingHandler extends Handler
{
private ?HandlerInterface $current_handler = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use ceLTIc\LTI\Tool;
use ceLTIc\LTI\User;
use ILIAS\HTTP\Wrapper\ArrayBasedRequestWrapper;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* LTI provider for LTI launch
Expand Down Expand Up @@ -87,6 +88,7 @@ public function parsePostBody(ArrayBasedRequestWrapper $postData): array
return $res;
}

#[AllowSuperglobalWrite('the celtic/lti library reads the request from the superglobals, so the parsed PSR-7 request has to be written back into them.', 12)]
public function handleRequest(?bool $strictMode = null, bool $disableCookieCheck = false, bool $generateWarnings = false): void
{
global $DIC;
Expand Down
2 changes: 2 additions & 0 deletions components/ILIAS/LTIProvider/resources/lti.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
declare(strict_types=1);


// @phpstan-ignore ilias.superglobalWrite.v12 (pins the command for the LTI entry point before ilInitialisation runs, so no HTTP service exists yet)
$_GET['cmd'] = 'post';
// @phpstan-ignore ilias.superglobalWrite.v12 (pins the command for the LTI entry point before ilInitialisation runs, so no HTTP service exists yet)
$_POST['cmd'] = 'doLTIAuthentication';

require_once '../vendor/composer/vendor/autoload.php';
Expand Down
3 changes: 1 addition & 2 deletions components/ILIAS/Test/classes/class.ilObjTestGUI.php
Original file line number Diff line number Diff line change
Expand Up @@ -2576,9 +2576,8 @@ public function createQuestionPoolAndCopyObject()
}

$question_pool = $this->createQuestionPool($title, $this->testrequest->raw('description'));
$_REQUEST['sel_qpl'] = $question_pool->getRefId();

$this->copyAndLinkQuestionsToPoolObject();
$this->copyAndLinkQuestionsToPoolObject($question_pool->getRefId());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*
*********************************************************************/

use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* @author Björn Heyser <bheyser@databay.de>
* @version $Id$
Expand Down Expand Up @@ -307,6 +309,7 @@ public function insert(ilTemplate $a_tpl): void
$this->tpl->addJavascript('assets/js/ilAssKprimChoice.js');
}

#[AllowSuperglobalWrite('flags a rejected image by writing a custom upload error into $_FILES, which the inherited getInput() then reports to the caller.', 12)]
public function checkUploads($foundvalues): bool
{
if (is_array($_FILES) && count($_FILES) && $this->getSingleline()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*
*********************************************************************/

use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* @author Björn Heyser <bheyser@databay.de>
*/
Expand Down Expand Up @@ -65,6 +67,7 @@ protected function isFileSubmitAvailable(): bool
return true;
}

#[AllowSuperglobalWrite('normalises the shape of $_FILES[postVar], because the inherited getInput() reads it back from there.', 12)]
protected function prepareFileSubmit(): void
{
$_FILES[$this->getPostVar()] = $this->prepareMultiFilesSubmitValues(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use ILIAS\UI\Renderer;
use ILIAS\UI\Component\Button\Factory as ButtonFactory;
use ILIAS\UI\Component\Symbol\Glyph\Factory as GlyphFactory;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* This class represents an image map file property in a property form.
Expand Down Expand Up @@ -177,6 +178,7 @@ private function getPostBody(): array
* Check input, strip slashes etc. set alert, if input is not ok.
* @return boolean Input ok, true/false
*/
#[AllowSuperglobalWrite('sanitises the uploaded file name in place, because the inherited getInput() hands $_FILES[postVar] on to every caller.', 12)]
public function checkInput(): bool
{
$lng = $this->lng;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*
*********************************************************************/

use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* @author Björn Heyser <bheyser@databay.de>
*/
Expand Down Expand Up @@ -64,6 +66,7 @@ public function manipulateFormSubmitValues(array $submitValues): array
/**
* perform the strip slashing on files submit
*/
#[AllowSuperglobalWrite('strips slashes in $_FILES[postVar], because the inherited getInput() reads it back from there.', 12)]
protected function manipulateFileSubmitValues(): void
{
if ($_FILES) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*
*********************************************************************/

use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* @author Björn Heyser <bheyser@databay.de>
*/
Expand Down Expand Up @@ -46,6 +48,7 @@ public function manipulateFormInputValues(array $inputValues): array
return $inputValues;
}

#[AllowSuperglobalWrite('normalises the shape of $_FILES[postVar], because the inherited getInput() reads it back from there.', 12)]
public function manipulateFormSubmitValues(array $submitValues): array
{
$submitValues = $this->removeAdditionalSubFieldsLevelFromSubmitValues($submitValues);
Expand Down
2 changes: 2 additions & 0 deletions components/ILIAS/UI/tests/Examples/ExamplesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use ILIAS\DI\Container;
use ILIAS\UI\NotImplementedException;
use ILIAS\FileUpload\FileUpload;
use ILIAS\Scripts\PHPStan\Attributes\AllowSuperglobalWrite;

/**
* Class ExamplesTest Checks if all examples are implemented and properly returning strings
Expand All @@ -47,6 +48,7 @@ class ExamplesTest extends ILIAS_UI_TestBase
protected Container $dic;
protected Crawler\ExamplesYamlParser $example_parser;

#[AllowSuperglobalWrite('Bypass Undefined index: ilfilehash for the moment. This is for examples only.', 12)]
public function setUp(): void
{
//This avoids various index not set warnings, which are only relevant in test context.
Expand Down
Loading
Loading