From 80f2c7ff530223ce702c2f26501bb7840619030b Mon Sep 17 00:00:00 2001 From: Uwe-Kohnle Date: Mon, 16 Feb 2026 21:09:10 +0000 Subject: [PATCH 1/5] fix xAPI sending statements (cherry picked from commit 60f8537459a420f9a60a822865c4bca2dc660b6c) --- .../classes/XapiProxy/XapiProxyRequest.php | 138 +++++++++++++----- .../classes/XapiProxy/XapiProxyResponse.php | 17 +-- .../ILIAS/CmiXapi/resources/xapiproxy.php | 3 + 3 files changed, 112 insertions(+), 46 deletions(-) diff --git a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php index b20fefdb1d9e..af0363d0f519 100755 --- a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php +++ b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php @@ -23,6 +23,7 @@ use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Uri; use ILIAS\DI\Container; +use GuzzleHttp\Psr7\Response; class XapiProxyRequest { @@ -250,13 +251,13 @@ private function handleProxy(\Psr\Http\Message\RequestInterface $request, $fakeP $body = $request->getBody()->getContents(); // Default request ausführen - $responseDefault = $this->sendCurlRequest($upstreamDefault, $authDefault, $request->getMethod(), $body); + $responseDefault = $this->sendCurlRequest($upstreamDefault, $authDefault, $request, $body); // Falls Fallback definiert, zweites Ziel testen $responseFallback = null; if ($hasFallback) { $upstreamFallback = $endpointFallback . $cmd; - $responseFallback = $this->sendCurlRequest($upstreamFallback, $authFallback, $request->getMethod(), $body); + $responseFallback = $this->sendCurlRequest($upstreamFallback, $authFallback, $request, $body); } // Reaktionen auswerten @@ -280,15 +281,43 @@ private function handleProxy(\Psr\Http\Message\RequestInterface $request, $fakeP } } - private function sendCurlRequest(string $url, string $authHeader, string $method, string $body = ''): \GuzzleHttp\Psr7\Response + private function sendCurlRequest(string $url, string $authHeader, \Psr\Http\Message\RequestInterface $request, string $body = ''): \GuzzleHttp\Psr7\Response { + $method = strtoupper($request->getMethod()); $ch = curl_init($url); $headers = [ "Authorization: $authHeader", - "X-Experience-API-Version: 1.0.3", - "Accept: application/json", - "Content-Type: application/json" +// "X-Experience-API-Version: 1.0.3", +// "Accept: application/json", +// "Content-Type: application/json", + "Cache-Control: no-cache, no-store, must-revalidate", + "Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With", + "Pragma: no-cache", ]; + if ($request->hasHeader('X-Experience-API-Version')) { + $headers[]= "X-Experience-API-Version: " . $request->getHeader('X-Experience-API-Version')[0]; + } + if ($request->hasHeader('Origin')) { + $headers[] = "Origin: " . $request->getHeader('Origin')[0]; + $headers[] = "Access-Control-Allow-Origin: " . $request->getHeader('Origin')[0]; + $headers[] = "Content-Security-Policy: frame-ancestors 'self' " . $request->getHeader('Origin')[0]; + } + if ($request->hasHeader('Referrer')) { + $headers[] = "Referrer: " . $request->getHeader('Referrer')[0]; + } + if ($request->hasHeader('Connection')) { + $headers[] = "Connection: " . $request->getHeader('Connection')[0]; + } +// if ($request->hasHeader('Content-Length')) { +// $contentLength = $request->getHeader('Content-Length'); +// if (is_array($contentLength) && $contentLength[0] != '') { +// $headers[] = "Content-Length: " . $contentLength[0]; +// } +// } + if ($request->hasHeader('Content-Type')) { + $headers[] = "Content-Type: " . $request->getHeader('Content-Type')[0]; + $headers[] = "Accept: application/json"; + } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, @@ -297,6 +326,7 @@ private function sendCurlRequest(string $url, string $authHeader, string $method CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_HTTPHEADER => $headers, +// CURLOPT_FAILONERROR => false, CURLOPT_CUSTOMREQUEST => strtoupper($method), ]); @@ -314,50 +344,92 @@ private function sendCurlRequest(string $url, string $authHeader, string $method $rawBody = ''; } - return new \GuzzleHttp\Psr7\Response($statusCode, [], $rawBody); - } - private function createProxyRequest(\Psr\Http\Message\RequestInterface $request, \GuzzleHttp\Psr7\Uri $uri, string $auth, string $body): \GuzzleHttp\Psr7\Request - { + //Headers to send to content $headers = array( 'Cache-Control' => 'no-cache, no-store, must-revalidate', - 'Authorization' => $auth + 'Content-Type' => "application/json", + 'Accept' => "application/json" ); if ($request->hasHeader('X-Experience-API-Version')) { $headers['X-Experience-API-Version'] = $request->getHeader('X-Experience-API-Version'); } - if ($request->hasHeader('Referrer')) { - $headers['Referrer'] = $request->getHeader('Referrer'); - } +// if ($request->hasHeader('Referrer')) { +// $headers['Referrer'] = $request->getHeader('Referrer'); +// } - if ($request->hasHeader('Content-Type')) { - $headers['Content-Type'] = $request->getHeader('Content-Type'); - } +// if ($request->hasHeader('Content-Type')) { +// $headers['Content-Type'] = $request->getHeader('Content-Type'); +// } if ($request->hasHeader('Origin')) { - $headers['Origin'] = $request->getHeader('Origin'); +// $headers['Origin'] = $request->getHeader('Origin'); + $headers['Access-Control-Allow-Origin'] = $request->getHeader('Origin'); + //$headers["Content-Security-Policy"] = "frame-ancestors 'self' " . $request->getHeader('Origin')[0]; } - if ($request->hasHeader('Content-Length')) { - $contentLength = $request->getHeader('Content-Length'); - if (is_array($contentLength) && $contentLength[0] === '') { - $contentLength = array(0); - } elseif ($contentLength === '') { - $contentLength = array(0); - } - $headers['Content-Length'] = $contentLength; - } +// if ($request->hasHeader('Content-Length')) { +// $contentLength = $request->getHeader('Content-Length'); +// if (is_array($contentLength) && $contentLength[0] === '') { +// $contentLength = array(0); +// } elseif ($contentLength === '') { +// $contentLength = array(0); +// } +// $headers['Content-Length'] = $contentLength; +// } - if ($request->hasHeader('Connection')) { - $headers['Connection'] = $request->getHeader('Connection'); - } +// if ($request->hasHeader('Connection')) { +// $headers['Connection'] = $request->getHeader('Connection'); +// } - //$this->xapiproxy->log()->debug($this->msg($body)); - $req = new Request(strtoupper($request->getMethod()), $uri, $headers, $body); - return $req; + return new \GuzzleHttp\Psr7\Response($statusCode, $headers, $rawBody); } + +// private function createProxyRequest(\Psr\Http\Message\RequestInterface $request, \GuzzleHttp\Psr7\Uri $uri, string $auth, string $body): \GuzzleHttp\Psr7\Request +// { +// $headers = array( +// 'Cache-Control' => 'no-cache, no-store, must-revalidate', +// 'Authorization' => $auth +// ); +// +// if ($request->hasHeader('X-Experience-API-Version')) { +// $headers['X-Experience-API-Version'] = $request->getHeader('X-Experience-API-Version'); +// } +// +// if ($request->hasHeader('Referrer')) { +// $headers['Referrer'] = $request->getHeader('Referrer'); +// } +// +// if ($request->hasHeader('Content-Type')) { +// $headers['Content-Type'] = $request->getHeader('Content-Type'); +// } +// +// if ($request->hasHeader('Origin')) { +// $headers['Origin'] = $request->getHeader('Origin'); +// } +// +// if ($request->hasHeader('Content-Length')) { +// $contentLength = $request->getHeader('Content-Length'); +// if (is_array($contentLength) && $contentLength[0] === '') { +// $contentLength = array(0); +// } elseif ($contentLength === '') { +// $contentLength = array(0); +// } +// $headers['Content-Length'] = $contentLength; +// } +// +// if ($request->hasHeader('Connection')) { +// $headers['Connection'] = $request->getHeader('Connection'); +// } +// +// //$this->xapiproxy->log()->debug($this->msg($body)); +// +// $req = new Request(strtoupper($request->getMethod()), $uri, $headers, $body); +// +// return $req; +// } } diff --git a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyResponse.php b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyResponse.php index 28236dc8da44..d228bc842646 100755 --- a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyResponse.php +++ b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyResponse.php @@ -39,20 +39,11 @@ public function __construct(XapiProxy $xapiproxy) public function checkResponse(\GuzzleHttp\Psr7\Response $response, string $endpoint): bool { - if ($response['state'] == 'fulfilled') { - $status = $response['value']->getStatusCode(); - if ($status === 200 || $status === 204 || $status === 404) { - return true; - } else { - $this->xapiproxy->log()->error("LRS error {$endpoint}: " . $response['value']->getBody()); - return false; - } + $status = $response->getStatusCode(); + if ($status === 200 || $status === 204 || $status === 404) { + return true; } else { - try { - $this->xapiproxy->log()->error("Connection error {$endpoint}: " . $response['reason']->getMessage()); - } catch (\Exception $e) { - $this->xapiproxy->log()->error("error {$endpoint}:" . $e->getMessage()); - } + $this->xapiproxy->log()->error("LRS error {$endpoint}: " . $response->getBody()); return false; } } diff --git a/components/ILIAS/CmiXapi/resources/xapiproxy.php b/components/ILIAS/CmiXapi/resources/xapiproxy.php index d86acf0a20c1..4ecba33a884f 100755 --- a/components/ILIAS/CmiXapi/resources/xapiproxy.php +++ b/components/ILIAS/CmiXapi/resources/xapiproxy.php @@ -61,6 +61,9 @@ require_once '../vendor/composer/vendor/autoload.php'; //instead of DataService \ilContext::init(\ilContext::CONTEXT_SCORM); +require_once __DIR__ . '/../artifacts/bootstrap_default.php'; +entry_point('ILIAS Legacy Initialisation Adapter'); + ilInitialisation::initILIAS(); // DataService::initIlias($client); $dic = $GLOBALS['DIC']; From bfc7d0df1bd0b2e18f7b8e2ba9f7346f8e82a988 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Fri, 23 Oct 2026 21:18:45 +0200 Subject: [PATCH 2/5] [Improvement] HTTP: add MessageFactory to create PSR-7 messages --- .../ILIAS/DI/tests/HTTPServicesTest.php | 3 +- components/ILIAS/HTTP/HTTP.php | 9 ++- components/ILIAS/HTTP/README.md | 31 ++++++++++ components/ILIAS/HTTP/src/GlobalHttpState.php | 3 + .../ILIAS/HTTP/src/Message/MessageFactory.php | 56 +++++++++++++++++ .../HTTP/src/Message/MessageFactoryImpl.php | 61 +++++++++++++++++++ components/ILIAS/HTTP/src/RawHTTPServices.php | 9 ++- components/ILIAS/HTTP/src/Services.php | 10 ++- .../classes/Dependencies/InitHttpServices.php | 5 ++ .../ILIAS/WebAccessChecker/resources/wac.php | 2 + 10 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 components/ILIAS/HTTP/src/Message/MessageFactory.php create mode 100644 components/ILIAS/HTTP/src/Message/MessageFactoryImpl.php diff --git a/components/ILIAS/DI/tests/HTTPServicesTest.php b/components/ILIAS/DI/tests/HTTPServicesTest.php index 46ab060843e8..7006a5efda46 100755 --- a/components/ILIAS/DI/tests/HTTPServicesTest.php +++ b/components/ILIAS/DI/tests/HTTPServicesTest.php @@ -22,6 +22,7 @@ use ILIAS\HTTP\Services; use ILIAS\HTTP\Request\RequestFactory; use ILIAS\HTTP\Response\ResponseFactory; +use ILIAS\HTTP\Message\MessageFactory; use ILIAS\HTTP\Response\Sender\ResponseSenderStrategy; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase as PHPUnitTestCase; @@ -80,7 +81,7 @@ protected function setUp(): void $this->mockDurationFactory = $this->createMock(DurationFactory::class); - $this->httpState = new RawHTTPServices($this->mockSenderStrategy, $this->mockCookieJarFactory, $this->mockRequestFactory, $this->mockResponseFactory, $this->mockDurationFactory); + $this->httpState = new RawHTTPServices($this->mockSenderStrategy, $this->mockCookieJarFactory, $this->mockRequestFactory, $this->mockResponseFactory, $this->mockDurationFactory, $this->createMock(MessageFactory::class)); } diff --git a/components/ILIAS/HTTP/HTTP.php b/components/ILIAS/HTTP/HTTP.php index 560cdeb94b90..f7de3a1bd3c8 100644 --- a/components/ILIAS/HTTP/HTTP.php +++ b/components/ILIAS/HTTP/HTTP.php @@ -43,6 +43,8 @@ use ILIAS\HTTP\Path\RequestHttpPathProvider; use ILIAS\HTTP\Path\ArtifactHttpPathProvider; use ILIAS\HTTP\Path\IniHttpPathProvider; +use ILIAS\HTTP\Message\MessageFactory; +use ILIAS\HTTP\Message\MessageFactoryImpl; class HTTP implements Component { @@ -62,6 +64,7 @@ public function init( $define[] = GlobalHttpState::class; $define[] = HeaderSettings::class; $define[] = HttpPathProvider::class; + $define[] = MessageFactory::class; $implement[HeaderSettings::class] = static fn() => new HeaderSettingsFromIni( $use[IliasIni::class] @@ -83,6 +86,9 @@ public function init( // RESPONSE FACTORY $implement[ResponseFactory::class] = static fn() => new ResponseFactoryImpl(); + // MESSAGE FACTORY + $implement[MessageFactory::class] = static fn() => new MessageFactoryImpl(); + // COOKIE JAR FACTORY $implement[CookieJarFactory::class] = static fn() => new CookieJarFactoryImpl(); @@ -100,7 +106,8 @@ public function init( $use[ResponseFactory::class], $use[CookieJarFactory::class], $internal[ResponseSenderStrategy::class], - $internal[DurationFactory::class] + $internal[DurationFactory::class], + $use[MessageFactory::class] ); // postMessage channel for pages talking to content of another origin, see README diff --git a/components/ILIAS/HTTP/README.md b/components/ILIAS/HTTP/README.md index ce2231054662..c05d1ccbe886 100755 --- a/components/ILIAS/HTTP/README.md +++ b/components/ILIAS/HTTP/README.md @@ -114,6 +114,37 @@ $response->withBody($body); $dic->http()->saveResponse($response); ``` +## Messages +### Description +Components which need further PSR-7 messages, e.g. to proxy a request to another server +or to emit a response of their own, create them with the `MessageFactory`. Components +MUST NOT instantiate classes of the underlying PSR-7 library (`GuzzleHttp\Psr7\*`) +directly and MUST only type against the `Psr\Http\Message\*` interfaces, so the library +stays an implementation detail of this component. + +### Usage +In a component bootstrap, use the service: +```php +$use[\ILIAS\HTTP\Message\MessageFactory::class] +``` + +In legacy code, it is available through the HTTP services: +```php +http()->messages(); + +//create a request +$request = $messages->request('POST', 'https://lrs.example.org/statements', ['Content-Type' => 'application/json'], $body); + +//create a response +$response = $messages->response(200, ['Content-Type' => 'application/json'], $body); + +//create an uri +$uri = $messages->uri('https://www.ilias.de'); +``` + ## Cookie Handling ### Description diff --git a/components/ILIAS/HTTP/src/GlobalHttpState.php b/components/ILIAS/HTTP/src/GlobalHttpState.php index 634f0e508327..7e2e14f72b25 100755 --- a/components/ILIAS/HTTP/src/GlobalHttpState.php +++ b/components/ILIAS/HTTP/src/GlobalHttpState.php @@ -25,6 +25,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use ILIAS\HTTP\Duration\DurationFactory; +use ILIAS\HTTP\Message\MessageFactory; use ILIAS\HTTP\Response\Sender\ResponseSenderStrategy; /** @@ -44,6 +45,8 @@ interface GlobalHttpState { public function durations(): DurationFactory; + public function messages(): MessageFactory; + public function wrapper(): WrapperFactory; /** diff --git a/components/ILIAS/HTTP/src/Message/MessageFactory.php b/components/ILIAS/HTTP/src/Message/MessageFactory.php new file mode 100644 index 000000000000..b4a5afec0ef4 --- /dev/null +++ b/components/ILIAS/HTTP/src/Message/MessageFactory.php @@ -0,0 +1,56 @@ + + */ +interface MessageFactory +{ + /** + * @param array $headers + */ + public function request( + string $method, + UriInterface|string $uri, + array $headers = [], + StreamInterface|string|null $body = null + ): RequestInterface; + + /** + * @param array $headers + */ + public function response( + int $status = 200, + array $headers = [], + StreamInterface|string|null $body = null + ): ResponseInterface; + + public function uri(string $uri = ''): UriInterface; +} diff --git a/components/ILIAS/HTTP/src/Message/MessageFactoryImpl.php b/components/ILIAS/HTTP/src/Message/MessageFactoryImpl.php new file mode 100644 index 000000000000..bc766f36cad0 --- /dev/null +++ b/components/ILIAS/HTTP/src/Message/MessageFactoryImpl.php @@ -0,0 +1,61 @@ + + */ +class MessageFactoryImpl implements MessageFactory +{ + public function request( + string $method, + UriInterface|string $uri, + array $headers = [], + StreamInterface|string|null $body = null + ): RequestInterface { + return new Request($method, $uri, $headers, $body); + } + + public function response( + int $status = 200, + array $headers = [], + StreamInterface|string|null $body = null + ): ResponseInterface { + return new Response($status, $headers, $body); + } + + public function uri(string $uri = ''): UriInterface + { + return new Uri($uri); + } +} diff --git a/components/ILIAS/HTTP/src/RawHTTPServices.php b/components/ILIAS/HTTP/src/RawHTTPServices.php index 1793378afc9a..1a9ab101ac29 100755 --- a/components/ILIAS/HTTP/src/RawHTTPServices.php +++ b/components/ILIAS/HTTP/src/RawHTTPServices.php @@ -25,6 +25,7 @@ use ILIAS\HTTP\Response\Sender\ResponseSenderStrategy; use ILIAS\HTTP\Wrapper\WrapperFactory; use ILIAS\HTTP\Duration\DurationFactory; +use ILIAS\HTTP\Message\MessageFactory; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -44,7 +45,8 @@ public function __construct( private CookieJarFactory $cookieJarFactory, private RequestFactory $requestFactory, private ResponseFactory $responseFactory, - private DurationFactory $durationFactory + private DurationFactory $durationFactory, + private MessageFactory $messageFactory ) { } @@ -58,6 +60,11 @@ public function durations(): DurationFactory return $this->durationFactory; } + public function messages(): MessageFactory + { + return $this->messageFactory; + } + public function wrapper(): WrapperFactory { return new WrapperFactory($this->request()); diff --git a/components/ILIAS/HTTP/src/Services.php b/components/ILIAS/HTTP/src/Services.php index b8ecad08df74..4447dbb2b27c 100755 --- a/components/ILIAS/HTTP/src/Services.php +++ b/components/ILIAS/HTTP/src/Services.php @@ -20,6 +20,7 @@ namespace ILIAS\HTTP; +use ILIAS\HTTP\Message\MessageFactory; use ILIAS\HTTP\Cookies\CookieJar; use ILIAS\HTTP\Wrapper\WrapperFactory; use Psr\Http\Message\ResponseInterface; @@ -52,13 +53,15 @@ public function __construct( CookieJarFactory $cookie_jar, ResponseSenderStrategy $response_sender_strategy, DurationFactory $duration_factory, + MessageFactory $message_factory, ) { $this->raw = new RawHTTPServices( $response_sender_strategy, $cookie_jar, $request_factory, $response_factory, - $duration_factory + $duration_factory, + $message_factory ); $this->agent = new AgentDetermination(); } @@ -73,6 +76,11 @@ public function durations(): DurationFactory return $this->raw->durations(); } + public function messages(): MessageFactory + { + return $this->raw->messages(); + } + public function wrapper(): WrapperFactory { return $this->wrapper ?? $this->wrapper = new WrapperFactory($this->raw->request()); diff --git a/components/ILIAS/Init/classes/Dependencies/InitHttpServices.php b/components/ILIAS/Init/classes/Dependencies/InitHttpServices.php index 04817fa83dd7..fadf2baab9df 100755 --- a/components/ILIAS/Init/classes/Dependencies/InitHttpServices.php +++ b/components/ILIAS/Init/classes/Dependencies/InitHttpServices.php @@ -16,6 +16,8 @@ * *********************************************************************/ +use ILIAS\HTTP\Message\MessageFactory; +use ILIAS\HTTP\Message\MessageFactoryImpl; use ILIAS\DI\Container; use ILIAS\HTTP\Request\RequestFactoryImpl; use ILIAS\HTTP\Response\ResponseFactoryImpl; @@ -49,6 +51,8 @@ public function init(Container $container): void $container[ResponseFactory::class] = static fn($c): ResponseFactoryImpl => new ResponseFactoryImpl(); + $container[MessageFactory::class] = static fn($c): MessageFactoryImpl => new MessageFactoryImpl(); + $container[CookieJarFactory::class] = static fn($c): CookieJarFactoryImpl => new CookieJarFactoryImpl(); $container[ResponseSenderStrategy::class] = static fn( @@ -69,6 +73,7 @@ public function init(Container $container): void $c[CookieJarFactory::class], $c[ResponseSenderStrategy::class], $c[DurationFactory::class], + $c[MessageFactory::class], ); $container['http'] = static fn($c): Services => $c[GlobalHttpState::class]; } diff --git a/components/ILIAS/WebAccessChecker/resources/wac.php b/components/ILIAS/WebAccessChecker/resources/wac.php index ba4ef3cfb5e6..0b317eb01a5f 100755 --- a/components/ILIAS/WebAccessChecker/resources/wac.php +++ b/components/ILIAS/WebAccessChecker/resources/wac.php @@ -31,6 +31,7 @@ use ILIAS\HTTP\Duration\Increment\IncrementFactory; use ILIAS\HTTP\Services; use ILIAS\HTTP\Cookies\CookieFactoryImpl; +use ILIAS\HTTP\Message\MessageFactoryImpl; /** @noRector */ require_once(__DIR__ . '/../vendor/composer/vendor/autoload.php'); @@ -58,6 +59,7 @@ $c['http.cookie_jar_factory'], $c['http.response_sender_strategy'], $c['http.duration_factory'], + new MessageFactoryImpl(), ); $GLOBALS["DIC"] = $container; From c9fadb224c07334ef9bf4fb1ffaa8ff4bb53147b Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Fri, 23 Oct 2026 21:18:45 +0200 Subject: [PATCH 3/5] [Improvement] use PSR-7 interfaces and HTTP MessageFactory instead of Guzzle classes --- components/ILIAS/Badge/tests/TileViewTest.php | 4 +- .../CmiXapi/classes/XapiProxy/XapiProxy.php | 5 +- .../classes/XapiProxy/XapiProxyRequest.php | 58 +++---------------- .../classes/XapiProxy/XapiProxyResponse.php | 17 +++--- .../ILIAS/CmiXapi/resources/xapiproxy.php | 2 +- .../Delivery/BaseDeliveryIsolationTest.php | 12 ++-- .../Delivery/LegacyDeliveryIsolationTest.php | 4 +- .../tests/Delivery/LegacyDeliveryTest.php | 19 ++++-- .../Delivery/StreamDeliveryIsolationTest.php | 4 +- .../tests/Delivery/StreamDeliveryTest.php | 19 ++++-- .../tests/Services/BaseURITest.php | 30 ++++++---- ...class.ilObjectCommonSettingFormAdapter.php | 6 +- .../class.ilIndividualAssessmentMemberGUI.php | 4 +- .../classes/Dependencies/InitCtrlService.php | 1 + .../StaticURL/tests/LegacyGotoHandlerTest.php | 2 +- .../tests/ShortlinkRequestBuilderTest.php | 2 +- .../class.ilObjStudyProgrammeMembersGUI.php | 2 +- .../Presentation/QuestionsBrowserTable.php | 1 - .../src/Scoring/Marks/class.MarkSchemaGUI.php | 4 +- .../class.SettingsScoringGUI.php | 26 +++++---- .../tests/Scoring/Marks/MarkSchemaGUITest.php | 2 +- .../Test/tests/ilTestBaseTestCaseTrait.php | 4 +- .../src/RequestDataCollector.php | 10 ++-- .../Navigation/Sequence/SequenceTest.php | 2 +- .../Component/Table/DataViewControlsTest.php | 2 +- .../Component/Table/TableRendererTestBase.php | 2 +- .../ILIAS/UICore/classes/class.ilCtrl.php | 5 +- .../ILIAS/UICore/tests/ilCtrlEventTest.php | 2 + components/ILIAS/UICore/tests/ilCtrlTest.php | 4 ++ 29 files changed, 124 insertions(+), 131 deletions(-) diff --git a/components/ILIAS/Badge/tests/TileViewTest.php b/components/ILIAS/Badge/tests/TileViewTest.php index aacb8078a8ca..277c5293d14d 100755 --- a/components/ILIAS/Badge/tests/TileViewTest.php +++ b/components/ILIAS/Badge/tests/TileViewTest.php @@ -20,9 +20,9 @@ namespace ILIAS\Badge\test; +use Psr\Http\Message\ServerRequestInterface; use ILIAS\DI\Container; use ILIAS\HTTP\Services; -use GuzzleHttp\Psr7\ServerRequest; use PHPUnit\Framework\TestCase; use ILIAS\Badge\Tile; use ILIAS\Badge\PresentationHeader; @@ -53,7 +53,7 @@ public function testConstruct(): void public function testShow(): void { - $request = $this->getMockBuilder(ServerRequest::class)->disableOriginalConstructor()->getMock(); + $request = $this->createMock(ServerRequestInterface::class); $request->method('getQueryParams')->willReturn([]); $http = $this->getMockBuilder(Services::class)->disableOriginalConstructor()->getMock(); diff --git a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxy.php b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxy.php index 5eaa16daabdc..54217e4c55b0 100755 --- a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxy.php +++ b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxy.php @@ -20,8 +20,7 @@ namespace XapiProxy; -use GuzzleHttp\Psr7\Request; -use GuzzleHttp\Psr7\Response; +use Psr\Http\Message\RequestInterface; class XapiProxy extends XapiProxyPolyFill { @@ -34,7 +33,7 @@ public function __construct(string $client, string $token, ?bool $plugin = false $this->log()->debug($this->msg('proxy initialized')); } - public function setRequestParams(Request $request): void + public function setRequestParams(RequestInterface $request): void { preg_match(self::PARTS_REG, (string) $request->getUri(), $this->cmdParts); } diff --git a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php index af0363d0f519..1786facb9b4f 100755 --- a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php +++ b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php @@ -20,10 +20,9 @@ namespace XapiProxy; -use GuzzleHttp\Psr7\Request; -use GuzzleHttp\Psr7\Uri; use ILIAS\DI\Container; -use GuzzleHttp\Psr7\Response; +use ILIAS\HTTP\Message\MessageFactory; +use Psr\Http\Message\ResponseInterface; class XapiProxyRequest { @@ -31,6 +30,8 @@ class XapiProxyRequest private XapiProxy $xapiproxy; + private MessageFactory $messages; + private XapiProxyResponse $xapiProxyResponse; private string $cmdPart2plus = ""; @@ -40,6 +41,7 @@ public function __construct(XapiProxy $xapiproxy) { $this->dic = $GLOBALS['DIC']; $this->xapiproxy = $xapiproxy; + $this->messages = $this->dic->http()->messages(); $this->cmdPart2plus = ""; } @@ -178,7 +180,7 @@ private function handlePostPutStatementsRequest(\Psr\Http\Message\RequestInterfa } try { $body = $this->xapiproxy->modifyBody($body); - $req = new Request($request->getMethod(), $request->getUri(), $request->getHeaders(), $body); + $req = $this->messages->request($request->getMethod(), $request->getUri(), $request->getHeaders(), $body); $this->handleProxy($req, $fakePostBody); } catch (\Exception $e) { $this->xapiproxy->log()->error($this->msg($e->getMessage())); @@ -281,7 +283,7 @@ private function handleProxy(\Psr\Http\Message\RequestInterface $request, $fakeP } } - private function sendCurlRequest(string $url, string $authHeader, \Psr\Http\Message\RequestInterface $request, string $body = ''): \GuzzleHttp\Psr7\Response + private function sendCurlRequest(string $url, string $authHeader, \Psr\Http\Message\RequestInterface $request, string $body = ''): ResponseInterface { $method = strtoupper($request->getMethod()); $ch = curl_init($url); @@ -386,50 +388,6 @@ private function sendCurlRequest(string $url, string $authHeader, \Psr\Http\Mess - return new \GuzzleHttp\Psr7\Response($statusCode, $headers, $rawBody); + return $this->messages->response($statusCode, $headers, $rawBody); } - -// private function createProxyRequest(\Psr\Http\Message\RequestInterface $request, \GuzzleHttp\Psr7\Uri $uri, string $auth, string $body): \GuzzleHttp\Psr7\Request -// { -// $headers = array( -// 'Cache-Control' => 'no-cache, no-store, must-revalidate', -// 'Authorization' => $auth -// ); -// -// if ($request->hasHeader('X-Experience-API-Version')) { -// $headers['X-Experience-API-Version'] = $request->getHeader('X-Experience-API-Version'); -// } -// -// if ($request->hasHeader('Referrer')) { -// $headers['Referrer'] = $request->getHeader('Referrer'); -// } -// -// if ($request->hasHeader('Content-Type')) { -// $headers['Content-Type'] = $request->getHeader('Content-Type'); -// } -// -// if ($request->hasHeader('Origin')) { -// $headers['Origin'] = $request->getHeader('Origin'); -// } -// -// if ($request->hasHeader('Content-Length')) { -// $contentLength = $request->getHeader('Content-Length'); -// if (is_array($contentLength) && $contentLength[0] === '') { -// $contentLength = array(0); -// } elseif ($contentLength === '') { -// $contentLength = array(0); -// } -// $headers['Content-Length'] = $contentLength; -// } -// -// if ($request->hasHeader('Connection')) { -// $headers['Connection'] = $request->getHeader('Connection'); -// } -// -// //$this->xapiproxy->log()->debug($this->msg($body)); -// -// $req = new Request(strtoupper($request->getMethod()), $uri, $headers, $body); -// -// return $req; -// } } diff --git a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyResponse.php b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyResponse.php index d228bc842646..75ba1602442e 100755 --- a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyResponse.php +++ b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyResponse.php @@ -22,8 +22,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; -use GuzzleHttp\Psr7\Response; -use GuzzleHttp\Psr7\Request; +use ILIAS\HTTP\Message\MessageFactory; class XapiProxyResponse { @@ -31,13 +30,15 @@ class XapiProxyResponse private XapiProxy $xapiproxy; //private $xapiProxyRequest; - public function __construct(XapiProxy $xapiproxy) - { + public function __construct( + XapiProxy $xapiproxy, + private MessageFactory $messages + ) { // $this->dic = $GLOBALS['DIC']; $this->xapiproxy = $xapiproxy; } - public function checkResponse(\GuzzleHttp\Psr7\Response $response, string $endpoint): bool + public function checkResponse(ResponseInterface $response, string $endpoint): bool { $status = $response->getStatusCode(); if ($status === 200 || $status === 204 || $status === 404) { @@ -48,7 +49,7 @@ public function checkResponse(\GuzzleHttp\Psr7\Response $response, string $endpo } } - public function handleResponse(\Psr\Http\Message\RequestInterface $request, Response $response, array|string|null $fakePostBody = null): void + public function handleResponse(\Psr\Http\Message\RequestInterface $request, ResponseInterface $response, array|string|null $fakePostBody = null): void { // check transfer encoding bug if ($fakePostBody !== null) { @@ -66,7 +67,7 @@ public function handleResponse(\Psr\Http\Message\RequestInterface $request, Resp $body = (string) $response->getBody(); unset($headers['Transfer-Encoding']); $headers['Content-Length'] = array(strlen($body)); - $response2 = new \GuzzleHttp\Psr7\Response($status, $headers, $body); + $response2 = $this->messages->response($status, $headers, $body); $this->emit($response2); } else { $this->emit($response); @@ -198,7 +199,7 @@ public function sendData(string $obj): void exit; } - public function emit(\GuzzleHttp\Psr7\Response $response): void + public function emit(ResponseInterface $response): void { $this->xapiproxy->log()->debug($this->msg('emitting response')); if (headers_sent()) { diff --git a/components/ILIAS/CmiXapi/resources/xapiproxy.php b/components/ILIAS/CmiXapi/resources/xapiproxy.php index 4ecba33a884f..ce8c0768a1ce 100755 --- a/components/ILIAS/CmiXapi/resources/xapiproxy.php +++ b/components/ILIAS/CmiXapi/resources/xapiproxy.php @@ -79,7 +79,7 @@ $xapiproxy->log()->error($e->getMessage()); } $req = new XapiProxyRequest($xapiproxy); -$resp = new XapiProxyResponse($xapiproxy); +$resp = new XapiProxyResponse($xapiproxy, $dic->http()->messages()); $xapiproxy->setXapiProxyRequest($req); $xapiproxy->setXapiProxyResponse($resp); diff --git a/components/ILIAS/FileDelivery/tests/Delivery/BaseDeliveryIsolationTest.php b/components/ILIAS/FileDelivery/tests/Delivery/BaseDeliveryIsolationTest.php index 1c15006bd2e2..36017b7d76a9 100644 --- a/components/ILIAS/FileDelivery/tests/Delivery/BaseDeliveryIsolationTest.php +++ b/components/ILIAS/FileDelivery/tests/Delivery/BaseDeliveryIsolationTest.php @@ -20,7 +20,7 @@ namespace ILIAS\Tests\FileDelivery\Delivery; -use GuzzleHttp\Psr7\Response; +use ILIAS\HTTP\Message\MessageFactoryImpl; use ILIAS\FileDelivery\Delivery\BaseDelivery; use ILIAS\FileDelivery\Delivery\Disposition; use ILIAS\FileDelivery\Delivery\ResponseBuilder\ResponseBuilder; @@ -138,7 +138,7 @@ public function testNotOnContentHostWhenRequestHitsIliasHost(): void public function testNoIsolationHeadersWhenDisabled(): void { $delivery = $this->delivery(IsolationConfig::disabled()); - $r = $delivery->exposedIsolationHeaders(new Response()); + $r = $delivery->exposedIsolationHeaders((new MessageFactoryImpl())->response()); $this->assertFalse($r->hasHeader('X-Content-Type-Options')); $this->assertFalse($r->hasHeader('Cross-Origin-Resource-Policy')); @@ -149,7 +149,7 @@ public function testNoIsolationHeadersWhenDisabled(): void public function testIsolationHeadersWhenActive(): void { $delivery = $this->delivery($this->activeConfig()); - $r = $delivery->exposedIsolationHeaders(new Response()); + $r = $delivery->exposedIsolationHeaders((new MessageFactoryImpl())->response()); $this->assertSame('nosniff', $r->getHeaderLine('X-Content-Type-Options')); $this->assertSame('cross-origin', $r->getHeaderLine('Cross-Origin-Resource-Policy')); @@ -161,7 +161,7 @@ public function testIsolationHeadersWhenActive(): void public function testGeneralHeadersCarryIsolationHeadersWhenActive(): void { $delivery = $this->delivery($this->activeConfig()); - $r = $delivery->exposedGeneralHeaders(new Response()); + $r = $delivery->exposedGeneralHeaders((new MessageFactoryImpl())->response()); $this->assertSame('nosniff', $r->getHeaderLine('X-Content-Type-Options')); $this->assertSame('cross-origin', $r->getHeaderLine('Cross-Origin-Resource-Policy')); @@ -173,7 +173,7 @@ public function testGeneralHeadersCarryIsolationHeadersWhenActive(): void public function testGeneralHeadersKeepTheirRegularContentHeaders(): void { $delivery = $this->delivery($this->activeConfig()); - $r = $delivery->exposedGeneralHeaders(new Response()); + $r = $delivery->exposedGeneralHeaders((new MessageFactoryImpl())->response()); $this->assertSame('text/plain', $r->getHeaderLine('Content-Type')); $this->assertSame( @@ -185,7 +185,7 @@ public function testGeneralHeadersKeepTheirRegularContentHeaders(): void public function testGeneralHeadersCarryNoIsolationHeadersWhenDisabled(): void { $delivery = $this->delivery(IsolationConfig::disabled()); - $r = $delivery->exposedGeneralHeaders(new Response()); + $r = $delivery->exposedGeneralHeaders((new MessageFactoryImpl())->response()); $this->assertSame('text/plain', $r->getHeaderLine('Content-Type')); $this->assertFalse($r->hasHeader('X-Content-Type-Options')); diff --git a/components/ILIAS/FileDelivery/tests/Delivery/LegacyDeliveryIsolationTest.php b/components/ILIAS/FileDelivery/tests/Delivery/LegacyDeliveryIsolationTest.php index 8f11969644bd..6607ae7cf5dd 100644 --- a/components/ILIAS/FileDelivery/tests/Delivery/LegacyDeliveryIsolationTest.php +++ b/components/ILIAS/FileDelivery/tests/Delivery/LegacyDeliveryIsolationTest.php @@ -20,7 +20,7 @@ namespace ILIAS\Tests\FileDelivery\Delivery; -use GuzzleHttp\Psr7\Response; +use ILIAS\HTTP\Message\MessageFactoryImpl; use ILIAS\FileDelivery\Delivery\LegacyDelivery; use ILIAS\FileDelivery\Delivery\ResponseBuilder\ResponseBuilder; use ILIAS\FileDelivery\Isolation\IsolationConfig; @@ -65,7 +65,7 @@ private function http(string $request_host): HttpServices $http = $this->createStub(HttpServices::class); $http->method('request')->willReturn($request); - $http->method('response')->willReturn(new Response()); + $http->method('response')->willReturn((new MessageFactoryImpl())->response()); $http->method('saveResponse')->willReturnCallback( function (ResponseInterface $r): void { $this->saved = $r; diff --git a/components/ILIAS/FileDelivery/tests/Delivery/LegacyDeliveryTest.php b/components/ILIAS/FileDelivery/tests/Delivery/LegacyDeliveryTest.php index 3a55cdede5dd..09accc9718e0 100644 --- a/components/ILIAS/FileDelivery/tests/Delivery/LegacyDeliveryTest.php +++ b/components/ILIAS/FileDelivery/tests/Delivery/LegacyDeliveryTest.php @@ -20,8 +20,9 @@ namespace ILIAS\Tests\FileDelivery\Delivery; -use GuzzleHttp\Psr7\Response; -use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ServerRequestInterface; +use ILIAS\HTTP\Message\MessageFactoryImpl; +use Psr\Http\Message\ResponseInterface; use ILIAS\FileDelivery\Delivery\LegacyDelivery; use ILIAS\FileDelivery\Delivery\ResponseBuilder\ResponseBuilder; use PHPUnit\Framework\MockObject\MockObject; @@ -66,10 +67,10 @@ public function testFileToKeepIsDeliveredByTheConfiguredBuilder(): void private function assertDeliveredBy(string $expected, bool $supports_deletion, bool $delete_file): void { - $response = new Response(); + $response = (new MessageFactoryImpl())->response(); $http = $this->createStub(\ILIAS\HTTP\Services::class); - $http->method('request')->willReturn(new ServerRequest('GET', 'https://ilias.example.org/')); + $http->method('request')->willReturn($this->requestTo('https://ilias.example.org/')); $http->method('response')->willReturn($response); // close() is never, the test leaves deliver() by an exception instead $http->method('close')->willThrowException(new \RuntimeException('closed')); @@ -86,7 +87,7 @@ private function assertDeliveredBy(string $expected, bool $supports_deletion, bo private function builder( string $name, bool $used, - Response $response, + ResponseInterface $response, bool $supports_deletion ): ResponseBuilder&MockObject { $builder = $this->createMock(ResponseBuilder::class); @@ -98,4 +99,12 @@ private function builder( return $builder; } + + private function requestTo(string $uri): ServerRequestInterface + { + $request = $this->createStub(ServerRequestInterface::class); + $request->method('getUri')->willReturn((new MessageFactoryImpl())->uri($uri)); + + return $request; + } } diff --git a/components/ILIAS/FileDelivery/tests/Delivery/StreamDeliveryIsolationTest.php b/components/ILIAS/FileDelivery/tests/Delivery/StreamDeliveryIsolationTest.php index 19834ebfbd97..f4ecda861800 100644 --- a/components/ILIAS/FileDelivery/tests/Delivery/StreamDeliveryIsolationTest.php +++ b/components/ILIAS/FileDelivery/tests/Delivery/StreamDeliveryIsolationTest.php @@ -20,7 +20,7 @@ namespace ILIAS\Tests\FileDelivery\Delivery; -use GuzzleHttp\Psr7\Response; +use ILIAS\HTTP\Message\MessageFactoryImpl; use ILIAS\FileDelivery\Delivery\Disposition; use ILIAS\FileDelivery\Delivery\ResponseBuilder\ResponseBuilder; use ILIAS\FileDelivery\Delivery\StreamDelivery; @@ -86,7 +86,7 @@ private function http(string $request_host): HttpServices $http = $this->createStub(HttpServices::class); $http->method('request')->willReturn($request); - $http->method('response')->willReturn(new Response()); + $http->method('response')->willReturn((new MessageFactoryImpl())->response()); $http->method('saveResponse')->willReturnCallback( function (ResponseInterface $r): void { $this->saved = $r; diff --git a/components/ILIAS/FileDelivery/tests/Delivery/StreamDeliveryTest.php b/components/ILIAS/FileDelivery/tests/Delivery/StreamDeliveryTest.php index bd8102771d78..85aa8cc12132 100644 --- a/components/ILIAS/FileDelivery/tests/Delivery/StreamDeliveryTest.php +++ b/components/ILIAS/FileDelivery/tests/Delivery/StreamDeliveryTest.php @@ -20,8 +20,9 @@ namespace ILIAS\Tests\FileDelivery\Delivery; -use GuzzleHttp\Psr7\Response; -use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use ILIAS\HTTP\Message\MessageFactoryImpl; use ILIAS\FileDelivery\Delivery\ResponseBuilder\ResponseBuilder; use ILIAS\FileDelivery\Delivery\StreamDelivery; use ILIAS\FileDelivery\Token\DataSigner; @@ -77,10 +78,10 @@ public function testMemoryStreamIsDeliveredByTheFallback(): void private function assertDeliveredBy(string $expected, FileStream $stream): void { - $response = new Response(); + $response = (new MessageFactoryImpl())->response(); $http = $this->createStub(\ILIAS\HTTP\Services::class); - $http->method('request')->willReturn(new ServerRequest('GET', 'https://ilias.example.org/')); + $http->method('request')->willReturn($this->requestTo('https://ilias.example.org/')); $http->method('response')->willReturn($response); // close() is never, the test leaves deliver() by an exception instead $http->method('close')->willThrowException(new \RuntimeException('closed')); @@ -99,7 +100,7 @@ private function assertDeliveredBy(string $expected, FileStream $stream): void $delivery->attached($stream, 'file.zip', 'application/zip'); } - private function builder(string $name, bool $used, Response $response): ResponseBuilder&MockObject + private function builder(string $name, bool $used, ResponseInterface $response): ResponseBuilder&MockObject { $builder = $this->createMock(ResponseBuilder::class); $builder->method('getName')->willReturn($name); @@ -109,4 +110,12 @@ private function builder(string $name, bool $used, Response $response): Response return $builder; } + + private function requestTo(string $uri): ServerRequestInterface + { + $request = $this->createStub(ServerRequestInterface::class); + $request->method('getUri')->willReturn((new MessageFactoryImpl())->uri($uri)); + + return $request; + } } diff --git a/components/ILIAS/FileDelivery/tests/Services/BaseURITest.php b/components/ILIAS/FileDelivery/tests/Services/BaseURITest.php index 8ae65af62865..c5db523dd90c 100644 --- a/components/ILIAS/FileDelivery/tests/Services/BaseURITest.php +++ b/components/ILIAS/FileDelivery/tests/Services/BaseURITest.php @@ -20,7 +20,7 @@ namespace ILIAS\Tests\FileDelivery\Services; -use GuzzleHttp\Psr7\ServerRequest; +use ILIAS\HTTP\Message\MessageFactoryImpl; use ILIAS\FileDelivery\Delivery\LegacyDelivery; use ILIAS\FileDelivery\Delivery\ResponseBuilder\ResponseBuilder; use ILIAS\FileDelivery\Delivery\StreamDelivery; @@ -42,7 +42,7 @@ class BaseURITest extends TestCase public function testHostIsTakenFromTheRequestAndPathFromTheHttpPath(): void { $services = $this->servicesFor( - new ServerRequest('GET', 'https://alias.example.org/ilias/index.php'), + $this->requestTo('https://alias.example.org/ilias/index.php'), 'https://ilias.example.org/ilias' ); @@ -52,7 +52,7 @@ public function testHostIsTakenFromTheRequestAndPathFromTheHttpPath(): void public function testPortOfTheRequestIsKept(): void { $services = $this->servicesFor( - new ServerRequest('GET', 'http://localhost:8080/ilias/index.php'), + $this->requestTo('http://localhost:8080/ilias/index.php'), 'http://localhost/ilias' ); @@ -65,7 +65,7 @@ public function testPortOfTheRequestIsKept(): void public function testHttpPathIsUsedWithoutARequest(): void { $services = $this->servicesFor( - new ServerRequest('GET', ''), + $this->requestTo(''), 'https://ilias.example.org/ilias/' ); @@ -79,7 +79,7 @@ public function testHttpPathIsUsedWithoutARequest(): void public function testPathIsTakenFromTheHttpPathOnARequestWithoutScript(): void { $services = $this->servicesFor( - new ServerRequest('GET', 'https://ilias.example.org/ilias/'), + $this->requestTo('https://ilias.example.org/ilias/'), 'https://ilias.example.org/ilias' ); @@ -89,7 +89,7 @@ public function testPathIsTakenFromTheHttpPathOnARequestWithoutScript(): void public function testPathIsTakenFromTheHttpPathOnARequestToASubdirectory(): void { $services = $this->servicesFor( - new ServerRequest('GET', 'https://ilias.example.org/ilias/Customizing/global/plugins/run.php'), + $this->requestTo('https://ilias.example.org/ilias/Customizing/global/plugins/run.php'), 'https://ilias.example.org/ilias' ); @@ -99,7 +99,7 @@ public function testPathIsTakenFromTheHttpPathOnARequestToASubdirectory(): void public function testHttpPathWithoutPathPointsToTheRoot(): void { $services = $this->servicesFor( - new ServerRequest('GET', 'https://ilias.example.org/'), + $this->requestTo('https://ilias.example.org/'), 'https://ilias.example.org' ); @@ -112,7 +112,7 @@ public function testHttpPathWithoutPathPointsToTheRoot(): void public function testPathIsTakenFromTheRequestWithoutHttpPath(): void { $services = $this->servicesFor( - new ServerRequest('GET', 'https://ilias.example.org/ilias/index.php'), + $this->requestTo('https://ilias.example.org/ilias/index.php'), null ); @@ -122,7 +122,7 @@ public function testPathIsTakenFromTheRequestWithoutHttpPath(): void public function testContentDomainIsUsedWithThePathOfTheHttpPath(): void { $services = $this->servicesFor( - new ServerRequest('GET', 'https://ilias.example.org/ilias/'), + $this->requestTo('https://ilias.example.org/ilias/'), 'https://ilias.example.org/ilias', $this->isolation() ); @@ -133,7 +133,7 @@ public function testContentDomainIsUsedWithThePathOfTheHttpPath(): void public function testContentDomainIsUsedWithoutARequest(): void { $services = $this->servicesFor( - new ServerRequest('GET', ''), + $this->requestTo(''), 'https://ilias.example.org/ilias/', $this->isolation() ); @@ -144,7 +144,7 @@ public function testContentDomainIsUsedWithoutARequest(): void public function testContentDomainTakesThePathFromTheRequestWithoutHttpPath(): void { $services = $this->servicesFor( - new ServerRequest('GET', 'https://ilias.example.org/ilias/index.php'), + $this->requestTo('https://ilias.example.org/ilias/index.php'), null, $this->isolation() ); @@ -191,4 +191,12 @@ private function baseURIOf(Services $services): string { return (new \ReflectionMethod($services, 'getBaseURI'))->invoke($services); } + + private function requestTo(string $uri): ServerRequestInterface + { + $request = $this->createStub(ServerRequestInterface::class); + $request->method('getUri')->willReturn((new MessageFactoryImpl())->uri($uri)); + + return $request; + } } diff --git a/components/ILIAS/ILIASObject/classes/CommonSettings/class.ilObjectCommonSettingFormAdapter.php b/components/ILIAS/ILIASObject/classes/CommonSettings/class.ilObjectCommonSettingFormAdapter.php index 187ef7c87271..34d0c04cfa89 100755 --- a/components/ILIAS/ILIASObject/classes/CommonSettings/class.ilObjectCommonSettingFormAdapter.php +++ b/components/ILIAS/ILIASObject/classes/CommonSettings/class.ilObjectCommonSettingFormAdapter.php @@ -26,7 +26,7 @@ use ILIAS\FileUpload\DTO\UploadResult; use ILIAS\ResourceStorage\Services as ResourceStorageServices; use ILIAS\HTTP\Services; -use GuzzleHttp\Psr7\UploadedFile; +use Psr\Http\Message\UploadedFileInterface; /** * @@ -123,8 +123,8 @@ public function saveTileImage(): void // this problem no longer have anyway. But at the moment we have to pick the right one out of all uploadResults, // that's only possible with this way - /** @var UploadedFile $file */ - $file = $this->http->request()->getUploadedFiles()['tile_image']; // Direct Access to the Psr7/UploadedFile + /** @var UploadedFileInterface $file */ + $file = $this->http->request()->getUploadedFiles()['tile_image']; // Direct Access to the Psr7 UploadedFile if ($file->getClientFilename() === '') { return; } diff --git a/components/ILIAS/IndividualAssessment/classes/class.ilIndividualAssessmentMemberGUI.php b/components/ILIAS/IndividualAssessment/classes/class.ilIndividualAssessmentMemberGUI.php index eb442fa4c5cc..372a98d73639 100755 --- a/components/ILIAS/IndividualAssessment/classes/class.ilIndividualAssessmentMemberGUI.php +++ b/components/ILIAS/IndividualAssessment/classes/class.ilIndividualAssessmentMemberGUI.php @@ -24,7 +24,7 @@ use ILIAS\FileUpload\Handler\BasicHandlerResult; use ILIAS\FileUpload\Handler\FileInfoResult; use ILIAS\FileUpload\Handler\HandlerResult; -use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ServerRequestInterface; use ILIAS\UI\Component\Input; use ILIAS\UI\Component\MessageBox; use ILIAS\UI\Component\Button; @@ -59,7 +59,7 @@ public function __construct( protected Refinery\Factory $refinery_factory, protected Data\Factory $data_factory, protected Renderer $renderer, - protected ServerRequest $request, + protected ServerRequestInterface $request, protected ilIndividualAssessmentPrimitiveInternalNotificator $notificator, protected ilToolbarGUI $toolbar, protected ilObjIndividualAssessment $object, diff --git a/components/ILIAS/Init/classes/Dependencies/InitCtrlService.php b/components/ILIAS/Init/classes/Dependencies/InitCtrlService.php index 971d812e5756..292b8addceb5 100755 --- a/components/ILIAS/Init/classes/Dependencies/InitCtrlService.php +++ b/components/ILIAS/Init/classes/Dependencies/InitCtrlService.php @@ -73,6 +73,7 @@ public function init(Container $dic): void $dic["component.factory"], new ilCtrlSubject(), new ilCtrlQueryRegexParser(), + $dic->http()->messages(), ); // add helper function to DI container that diff --git a/components/ILIAS/StaticURL/tests/LegacyGotoHandlerTest.php b/components/ILIAS/StaticURL/tests/LegacyGotoHandlerTest.php index a35c6f00e13b..8e014b6f802a 100755 --- a/components/ILIAS/StaticURL/tests/LegacyGotoHandlerTest.php +++ b/components/ILIAS/StaticURL/tests/LegacyGotoHandlerTest.php @@ -93,7 +93,7 @@ public static function urlProvider(): \Iterator #[DataProvider('urlProvider')] public function testBase(string $called_url, string $target): void { - $this->http_mock->request()->method('getUri')->willReturn(new \GuzzleHttp\Psr7\Uri($called_url)); + $this->http_mock->request()->method('getUri')->willReturn((new \ILIAS\HTTP\Message\MessageFactoryImpl())->uri($called_url)); $this->assertEquals($called_url, $this->http_mock->request()->getUri()); $uri = new URI($called_url); diff --git a/components/ILIAS/StaticURL/tests/ShortlinkRequestBuilderTest.php b/components/ILIAS/StaticURL/tests/ShortlinkRequestBuilderTest.php index b99578ff681d..0fdf5076b84f 100644 --- a/components/ILIAS/StaticURL/tests/ShortlinkRequestBuilderTest.php +++ b/components/ILIAS/StaticURL/tests/ShortlinkRequestBuilderTest.php @@ -70,7 +70,7 @@ protected function setUp(): void private function prepareRequest(string $called_url): void { $uri = new URI($called_url); - $this->request_stub->method('getUri')->willReturn(new \GuzzleHttp\Psr7\Uri($called_url)); + $this->request_stub->method('getUri')->willReturn((new \ILIAS\HTTP\Message\MessageFactoryImpl())->uri($called_url)); $this->request_stub->method('getQueryParams')->willReturn($uri->getParameters()); $this->http_stub->method('wrapper')->willReturn(new WrapperFactory($this->request_stub)); } diff --git a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php index 3b0dad229b60..8a4a5756df2f 100755 --- a/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php +++ b/components/ILIAS/StudyProgramme/classes/class.ilObjStudyProgrammeMembersGUI.php @@ -77,7 +77,7 @@ public function __construct( protected ILIAS\Refinery\Factory $refinery, protected ILIAS\UI\Factory $ui_factory, protected ILIAS\UI\Renderer $ui_renderer, - protected GuzzleHttp\Psr7\ServerRequest $request, + protected Psr\Http\Message\ServerRequestInterface $request, ) { $this->object = null; $this->permissions = null; diff --git a/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserTable.php b/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserTable.php index e2ab7e31744d..c5c60092809f 100644 --- a/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserTable.php +++ b/components/ILIAS/Test/src/Questions/Presentation/QuestionsBrowserTable.php @@ -21,7 +21,6 @@ namespace ILIAS\Test\Questions\Presentation; use ILIAS\Taxonomy\DomainService as TaxonomyService; -use GuzzleHttp\Psr7\ServerRequest; use ILIAS\Data\Factory as DataFactory; use ILIAS\Data\Order; use ILIAS\Data\Range; diff --git a/components/ILIAS/Test/src/Scoring/Marks/class.MarkSchemaGUI.php b/components/ILIAS/Test/src/Scoring/Marks/class.MarkSchemaGUI.php index 613ee3a78d20..0d56f3eeaf42 100644 --- a/components/ILIAS/Test/src/Scoring/Marks/class.MarkSchemaGUI.php +++ b/components/ILIAS/Test/src/Scoring/Marks/class.MarkSchemaGUI.php @@ -24,7 +24,7 @@ use ILIAS\Test\Logging\TestAdministrationInteractionTypes; use ILIAS\Test\ResponseHandler; use ILIAS\HTTP\Wrapper\RequestWrapper; -use GuzzleHttp\Psr7\Request; +use Psr\Http\Message\ServerRequestInterface; use ILIAS\Refinery\Factory as Refinery; use ILIAS\Data\Factory as DataFactory; use ILIAS\UI\URLBuilder; @@ -58,7 +58,7 @@ public function __construct( private RequestWrapper $post_wrapper, private RequestWrapper $request_wrapper, private ResponseHandler $response_handler, - private Request $request, + private ServerRequestInterface $request, private Refinery $refinery, private UIFactory $ui_factory, private UIRenderer $ui_renderer, diff --git a/components/ILIAS/Test/src/Settings/ScoreReporting/class.SettingsScoringGUI.php b/components/ILIAS/Test/src/Settings/ScoreReporting/class.SettingsScoringGUI.php index 95a49d487ef7..0d3fbf9db824 100755 --- a/components/ILIAS/Test/src/Settings/ScoreReporting/class.SettingsScoringGUI.php +++ b/components/ILIAS/Test/src/Settings/ScoreReporting/class.SettingsScoringGUI.php @@ -178,21 +178,22 @@ private function saveForm(): void $this->ctrl->redirect($this, self::CMD_SHOW_FORM); } + /** + * Restores the submitted form data, which has been relayed through the + * confirmation, on the current request. + */ private function getRelayedRequest(): Request { - return unserialize( + $relayed_body = json_decode( base64_decode( - $this->request->getParsedBody()[self::F_CONFIRM_SETTINGS] + (string) ($this->request->getParsedBody()[self::F_CONFIRM_SETTINGS] ?? '') ), - [ - 'allowed_classes' => [ - \GuzzleHttp\Psr7\ServerRequest::class, - \GuzzleHttp\Psr7\Uri::class, - \GuzzleHttp\Psr7\UploadedFile::class, - \GuzzleHttp\Psr7\Stream::class, - ] - ] + true, + 512, + JSON_THROW_ON_ERROR ); + + return $this->request->withParsedBody(is_array($relayed_body) ? $relayed_body : []); } private function buildForm(): Form @@ -303,7 +304,10 @@ private function showConfirmation(Request $request) $confirmation->setFormAction($this->ctrl->getFormAction($this)); $confirmation->setCancel($this->lng->txt('cancel'), self::CMD_CANCEL_RECALC); $confirmation->setConfirm($this->lng->txt('confirm'), self::CMD_CONFIRMED_RECALC); - $confirmation->addHiddenItem(self::F_CONFIRM_SETTINGS, base64_encode(serialize($request))); + $confirmation->addHiddenItem( + self::F_CONFIRM_SETTINGS, + base64_encode(json_encode($request->getParsedBody(), JSON_THROW_ON_ERROR)) + ); $this->tpl->setContent($this->ctrl->getHTML($confirmation)); } } diff --git a/components/ILIAS/Test/tests/Scoring/Marks/MarkSchemaGUITest.php b/components/ILIAS/Test/tests/Scoring/Marks/MarkSchemaGUITest.php index f08fa4d56600..7a8713583acf 100755 --- a/components/ILIAS/Test/tests/Scoring/Marks/MarkSchemaGUITest.php +++ b/components/ILIAS/Test/tests/Scoring/Marks/MarkSchemaGUITest.php @@ -46,7 +46,7 @@ protected function setUp(): void $this->createMock(ILIAS\HTTP\Wrapper\RequestWrapper::class), $this->createMock(ILIAS\HTTP\Wrapper\RequestWrapper::class), $this->createMock(ILIAS\Test\ResponseHandler::class), - $this->createMock(\GuzzleHttp\Psr7\Request::class), + $this->createMock(\Psr\Http\Message\ServerRequestInterface::class), $this->createMock(ILIAS\Refinery\Factory::class), $this->createMock(ILIAS\UI\Factory::class), $this->createMock(ILIAS\UI\Renderer::class), diff --git a/components/ILIAS/Test/tests/ilTestBaseTestCaseTrait.php b/components/ILIAS/Test/tests/ilTestBaseTestCaseTrait.php index 5cad50136567..a570540d518d 100644 --- a/components/ILIAS/Test/tests/ilTestBaseTestCaseTrait.php +++ b/components/ILIAS/Test/tests/ilTestBaseTestCaseTrait.php @@ -18,6 +18,7 @@ declare(strict_types=1); +use ILIAS\HTTP\Message\MessageFactoryImpl; use ILIAS\Administration\Setting; use ILIAS\Test\RequestDataCollector; use PHPUnit\Framework\MockObject\MockObject; @@ -37,7 +38,6 @@ use ILIAS\UI\Implementation\FactoryInternal; use ILIAS\UI\Implementation\Factory as ImplementationFactory; use ILIAS\User\PublicInterface; -use GuzzleHttp\Psr7\Uri as GuzzleURI; use PHPUnit\Framework\MockObject\Rule\InvocationOrder; trait ilTestBaseTestCaseTrait @@ -335,7 +335,7 @@ protected function addGlobal_http(): void ->disableOriginalConstructor() ->getMock(); $request_mock->method('getUri') - ->willReturn(new GuzzleURI('http://wwww.ilias.de')); + ->willReturn((new MessageFactoryImpl())->uri('http://wwww.ilias.de')); $http_mock = $this->getMockBuilder(HTTPServices::class)->disableOriginalConstructor() ->getMock(); $http_mock->method('request') diff --git a/components/ILIAS/TestQuestionPool/src/RequestDataCollector.php b/components/ILIAS/TestQuestionPool/src/RequestDataCollector.php index 45ce3a4a280a..50f23716e10b 100755 --- a/components/ILIAS/TestQuestionPool/src/RequestDataCollector.php +++ b/components/ILIAS/TestQuestionPool/src/RequestDataCollector.php @@ -18,7 +18,7 @@ namespace ILIAS\TestQuestionPool; -use GuzzleHttp\Psr7\UploadedFile; +use Psr\Http\Message\UploadedFileInterface; use ILIAS\FileUpload\DTO\UploadResult; use ILIAS\Refinery\Transformation; use ILIAS\Repository\BaseGUIRequest; @@ -79,13 +79,11 @@ public function getUploadFilename(array $http_names, int $index): ?string $uploaded_files = $uploaded_files[$current_key]; if (isset($uploaded_files[$index]) && $http_names === []) { - /** @var UploadedFile $file */ + /** @var UploadedFileInterface $file */ $file = $uploaded_files[$index]; - $c = \Closure::bind(static function (UploadedFile $file): ?string { - return $file->file ?? null; - }, null, $file); - return $c($file); + // same identifier as used by the FileUpload service for its results + return $file->getStream()->getMetadata('uri'); } } diff --git a/components/ILIAS/UI/tests/Component/Navigation/Sequence/SequenceTest.php b/components/ILIAS/UI/tests/Component/Navigation/Sequence/SequenceTest.php index 3c0c39311a7e..4e6bbf6fe01f 100644 --- a/components/ILIAS/UI/tests/Component/Navigation/Sequence/SequenceTest.php +++ b/components/ILIAS/UI/tests/Component/Navigation/Sequence/SequenceTest.php @@ -57,7 +57,7 @@ public function setUp(): void $this->request = $this->createMock(ServerRequestInterface::class); $this->request->method("getUri") - ->willReturn(new \GuzzleHttp\Psr7\Uri('http://localhost:80/doil')); + ->willReturn((new \ILIAS\HTTP\Message\MessageFactoryImpl())->uri('http://localhost:80/doil')); parent::setUp(); } diff --git a/components/ILIAS/UI/tests/Component/Table/DataViewControlsTest.php b/components/ILIAS/UI/tests/Component/Table/DataViewControlsTest.php index f57f8693014f..ead9f84315d3 100755 --- a/components/ILIAS/UI/tests/Component/Table/DataViewControlsTest.php +++ b/components/ILIAS/UI/tests/Component/Table/DataViewControlsTest.php @@ -207,7 +207,7 @@ private function getRequestMock(array $returns): ServerRequestInterface $request = $this->createMock(ServerRequestInterface::class); $request ->method("getUri") - ->willReturn(new \GuzzleHttp\Psr7\Uri('http://localhost:80')); + ->willReturn((new \ILIAS\HTTP\Message\MessageFactoryImpl())->uri('http://localhost:80')); $request ->method("getQueryParams") ->willReturn($returns); diff --git a/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php b/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php index 728bcc1cc7bd..d44e6c208b43 100644 --- a/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php +++ b/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php @@ -50,7 +50,7 @@ protected function getDummyRequest() $request = $this->createMock(ServerRequestInterface::class); $request ->method("getUri") - ->willReturn(new \GuzzleHttp\Psr7\Uri('http://localhost:80')); + ->willReturn((new \ILIAS\HTTP\Message\MessageFactoryImpl())->uri('http://localhost:80')); $request ->method("getQueryParams") ->willReturn([]); diff --git a/components/ILIAS/UICore/classes/class.ilCtrl.php b/components/ILIAS/UICore/classes/class.ilCtrl.php index 38d5cd8c4114..b8ac892a3dee 100755 --- a/components/ILIAS/UICore/classes/class.ilCtrl.php +++ b/components/ILIAS/UICore/classes/class.ilCtrl.php @@ -24,7 +24,7 @@ use ILIAS\HTTP\Wrapper\RequestWrapper; use ILIAS\Filesystem\Stream\Streams; use Psr\Http\Message\ServerRequestInterface; -use GuzzleHttp\Psr7\Response; +use ILIAS\HTTP\Message\MessageFactory; /** * Class ilCtrl provides processing control methods. A global @@ -59,6 +59,7 @@ public function __construct( protected ilComponentFactory $component_factory, protected ilCtrlSubject $subject, protected ilCtrlQueryParserInterface $query_parser, + protected MessageFactory $message_factory, ) { } @@ -437,7 +438,7 @@ public function redirectToURL(string $target_url): void $target_url = $this->modifyUrlWithPluginHooks($target_url); // initialize http response object - $response = new Response(); + $response = $this->message_factory->response(); // there's an exceptional case for asynchronous file uploads // where a json response is delivered. diff --git a/components/ILIAS/UICore/tests/ilCtrlEventTest.php b/components/ILIAS/UICore/tests/ilCtrlEventTest.php index 00a52c8065ea..04731cb4022d 100755 --- a/components/ILIAS/UICore/tests/ilCtrlEventTest.php +++ b/components/ILIAS/UICore/tests/ilCtrlEventTest.php @@ -18,6 +18,7 @@ declare(strict_types=1); +use ILIAS\HTTP\Message\MessageFactory; use PHPUnit\Framework\TestCase; use ILIAS\HTTP\Wrapper\ArrayBasedRequestWrapper; use ILIAS\Refinery\Factory as Refinery; @@ -131,6 +132,7 @@ private function getMockedCtrl(?ilCtrlSubject $subject = null): ilCtrlInterface $this->createStub(ilComponentFactory::class), $subject ?? $this->subject, $this->createStub(ilCtrlQueryParserInterface::class), + $this->createStub(MessageFactory::class), ); } } diff --git a/components/ILIAS/UICore/tests/ilCtrlTest.php b/components/ILIAS/UICore/tests/ilCtrlTest.php index 2ccb0fb563e9..7baea59bd82c 100755 --- a/components/ILIAS/UICore/tests/ilCtrlTest.php +++ b/components/ILIAS/UICore/tests/ilCtrlTest.php @@ -18,6 +18,7 @@ declare(strict_types=1); +use ILIAS\HTTP\Message\MessageFactory; use PHPUnit\Framework\TestCase; use ILIAS\HTTP\Response\Sender\ResponseSenderStrategy; use Psr\Http\Message\ServerRequestInterface; @@ -61,6 +62,7 @@ public function testCallBaseClassWithInvalidProvidedBaseClass(): void $this->createStub(ilComponentFactory::class), new ilCtrlSubject(), $this->createStub(ilCtrlQueryParserInterface::class), + $this->createStub(MessageFactory::class), ); $invalid_baseclass = ilCtrlInvalidGuiClass::class; @@ -145,6 +147,7 @@ public function testGetCmdWithoutProvidedCmdAndFallback(): void $this->createStub(ilComponentFactory::class), new ilCtrlSubject(), $this->createStub(ilCtrlQueryParserInterface::class), + $this->createStub(MessageFactory::class), ); $fallback_cmd = 'fallback_cmd_test'; @@ -235,6 +238,7 @@ private function getMockedCtrl(): ilCtrlInterface $this->createStub(ilComponentFactory::class), new ilCtrlSubject(), $this->createStub(ilCtrlQueryParserInterface::class), + $this->createStub(MessageFactory::class), ); } } From 985261df96649d1cdc5d41cd4d2f15005619d780 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Fri, 23 Oct 2026 21:18:45 +0200 Subject: [PATCH 4/5] [Improvement] PHPStan: forbid wrapped libraries outside of their component --- .../Attributes/AllowWrappedLibraryUsage.php | 46 ++++ scripts/PHPStan/README.md | 2 + .../WrappedLibraryUsageRule.php | 231 ++++++++++++++++++ scripts/PHPStan/code_rules.neon | 1 + scripts/PHPStan/list_exemptions.php | 12 +- 5 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 scripts/PHPStan/Attributes/AllowWrappedLibraryUsage.php create mode 100644 scripts/PHPStan/Rules/WrappedLibraries/WrappedLibraryUsageRule.php diff --git a/scripts/PHPStan/Attributes/AllowWrappedLibraryUsage.php b/scripts/PHPStan/Attributes/AllowWrappedLibraryUsage.php new file mode 100644 index 000000000000..612b3887040b --- /dev/null +++ b/scripts/PHPStan/Attributes/AllowWrappedLibraryUsage.php @@ -0,0 +1,46 @@ + + */ +final class WrappedLibraryUsageRule implements Rule +{ + public const IDENTIFIER = 'ilias.wrappedLibraryUsage'; + + public const LABEL = 'Wrapped library usage'; + + /** + * Namespace of the wrapped library => the component (below components/ILIAS) that + * wraps it, and a hint what to use instead. + * + * @var array + */ + private const WRAPPED_LIBRARIES = [ + 'GuzzleHttp\\Psr7' => [ + 'component' => 'HTTP', + 'hint' => 'Type against the Psr\\Http\\Message interfaces and create messages with ILIAS\\HTTP\\Message\\MessageFactory.', + ], + ]; + + public function __construct( + private readonly ReflectionProvider $reflection_provider + ) { + } + + /** + * The reported identifier, carrying the ILIAS major the analysis runs against, so + * inline ignores expire with the release they were written for. + */ + public static function identifier(): string + { + return self::IDENTIFIER . '.v' . IliasVersion::major(); + } + + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($this->referencedNames($node, $scope) as [$name, $line]) { + $library = $this->wrappedLibraryOf($name); + if ($library === null || $this->isInComponent($scope->getFile(), $library['component'])) { + continue; + } + if (RuleViolationAllowance::isAllowedIn($scope, self::IDENTIFIER)) { + return []; + } + + $errors[] = RuleErrorBuilder::message( + "{$name} belongs to a library wrapped by the {$library['component']} component " + . "and must not be used outside of it. {$library['hint']}" + ) + ->identifier(self::identifier()) + ->line($line) + ->metadata([ + 'rule' => self::LABEL, + 'version' => 12, + ]) + ->build(); + } + + return $errors; + } + + /** + * @return list the fully qualified names referenced directly by + * the node, each with its line + */ + private function referencedNames(Node $node, Scope $scope): array + { + $names = match (true) { + $node instanceof Expr\New_, + $node instanceof Expr\StaticCall, + $node instanceof Expr\StaticPropertyFetch, + $node instanceof Expr\ClassConstFetch, + $node instanceof Expr\Instanceof_ => [$node->class], + $node instanceof Stmt\Catch_ => $node->types, + $node instanceof Stmt\TraitUse => $node->traits, + $node instanceof InClassNode => $this->classHeaderNames($node->getOriginalNode()), + $node instanceof InClassMethodNode, + $node instanceof InFunctionNode, + $node instanceof InClosureNode, + $node instanceof InArrowFunctionNode => $this->signatureNames($node->getOriginalNode()), + $node instanceof ClassPropertyNode => $this->typeNames($node->getNativeTypeNode()), + default => [], + }; + + $referenced = []; + foreach ($names as $name) { + if (!$name instanceof Name || $name->isSpecialClassName()) { + continue; + } + $referenced[] = [ltrim($name->toString(), '\\'), $name->getStartLine()]; + } + + if ($node instanceof Expr\FuncCall && $node->name instanceof Name) { + $function = $this->reflection_provider->resolveFunctionName($node->name, $scope); + if ($function !== null) { + $referenced[] = [$function, $node->name->getStartLine()]; + } + } + + return $referenced; + } + + /** + * @return list + */ + private function classHeaderNames(Stmt\ClassLike $class): array + { + return match (true) { + $class instanceof Stmt\Class_ => [...($class->extends === null ? [] : [$class->extends]), ...$class->implements], + $class instanceof Stmt\Interface_ => $class->extends, + $class instanceof Stmt\Enum_ => $class->implements, + default => [], + }; + } + + /** + * @return list + */ + private function signatureNames(Stmt\ClassMethod|Stmt\Function_|Expr\Closure|Expr\ArrowFunction $function): array + { + $names = $this->typeNames($function->returnType); + foreach ($function->params as $param) { + $names = [...$names, ...$this->typeNames($param->type)]; + } + + return $names; + } + + /** + * @return list + */ + private function typeNames(?Node $type): array + { + return match (true) { + $type instanceof Name => [$type], + $type instanceof Node\NullableType => $this->typeNames($type->type), + $type instanceof Node\UnionType, + $type instanceof Node\IntersectionType => array_merge(...array_map($this->typeNames(...), $type->types)), + default => [], + }; + } + + /** + * @return array{component: string, hint: string}|null + */ + private function wrappedLibraryOf(string $name): ?array + { + foreach (self::WRAPPED_LIBRARIES as $namespace => $library) { + if (stripos($name, $namespace . '\\') === 0) { + return $library; + } + } + + return null; + } + + private function isInComponent(string $file, string $component): bool + { + return str_contains( + str_replace('\\', '/', $file), + '/components/ILIAS/' . $component . '/' + ); + } +} diff --git a/scripts/PHPStan/code_rules.neon b/scripts/PHPStan/code_rules.neon index 1cdec72f7d77..5daa069900e0 100644 --- a/scripts/PHPStan/code_rules.neon +++ b/scripts/PHPStan/code_rules.neon @@ -2,6 +2,7 @@ rules: - ILIAS\Scripts\PHPStan\Rules\SuperGlobals\SuperglobalAssignRule - ILIAS\Scripts\PHPStan\Rules\SuperGlobals\SuperglobalAssignOpRule - ILIAS\Scripts\PHPStan\Rules\SuperGlobals\SuperglobalAssignRefRule + - ILIAS\Scripts\PHPStan\Rules\WrappedLibraries\WrappedLibraryUsageRule services: errorFormatter.stepSummary: class: ILIAS\Scripts\PHPStan\ErrorFormatter\StepSummaryFormatter diff --git a/scripts/PHPStan/list_exemptions.php b/scripts/PHPStan/list_exemptions.php index a245de2a15be..3652472c715c 100644 --- a/scripts/PHPStan/list_exemptions.php +++ b/scripts/PHPStan/list_exemptions.php @@ -38,7 +38,13 @@ require_once __DIR__ . '/IliasVersion.php'; /** Attribute short names that grant an exemption. */ -const EXEMPTION_ATTRIBUTES = ['AllowRuleViolation', 'AllowSuperglobalWrite']; +const EXEMPTION_ATTRIBUTES = ['AllowRuleViolation', 'AllowSuperglobalWrite', 'AllowWrappedLibraryUsage']; + +/** Rule identifier carried by the convenience attributes. */ +const CONVENIENCE_ATTRIBUTE_RULES = [ + 'AllowSuperglobalWrite' => 'ilias.superglobalWrite', + 'AllowWrappedLibraryUsage' => 'ilias.wrappedLibraryUsage', +]; $target = 'components/ILIAS'; $against = IliasVersion::major(); @@ -135,7 +141,7 @@ private function collect(Node\Stmt\ClassLike|Node\Stmt\ClassMethod|Node\Stmt\Fun } if ($rules === []) { - $rules = [$short === 'AllowSuperglobalWrite' ? 'ilias.superglobalWrite' : '(unspecified)']; + $rules = [CONVENIENCE_ATTRIBUTE_RULES[$short] ?? '(unspecified)']; } $this->found[] = [ @@ -196,7 +202,7 @@ private static function flatten(string $text): string $code = (string) file_get_contents($path); // attribute-based exemptions - if (str_contains($code, 'AllowRuleViolation') || str_contains($code, 'AllowSuperglobalWrite')) { + if (array_filter(EXEMPTION_ATTRIBUTES, static fn(string $attribute): bool => str_contains($code, $attribute)) !== []) { try { $ast = $parser->parse($code); } catch (\Throwable $e) { From d965d13e1a5a0478a2d991b401491e54f381c711 Mon Sep 17 00:00:00 2001 From: Fabian Schmid Date: Fri, 23 Oct 2026 21:26:58 +0200 Subject: [PATCH 5/5] [Improvement] CmiXapi: apply code style to XapiProxyRequest --- .../classes/XapiProxy/XapiProxyRequest.php | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php index 1786facb9b4f..05edaaa8bbe8 100755 --- a/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php +++ b/components/ILIAS/CmiXapi/classes/XapiProxy/XapiProxyRequest.php @@ -297,7 +297,7 @@ private function sendCurlRequest(string $url, string $authHeader, \Psr\Http\Mess "Pragma: no-cache", ]; if ($request->hasHeader('X-Experience-API-Version')) { - $headers[]= "X-Experience-API-Version: " . $request->getHeader('X-Experience-API-Version')[0]; + $headers[] = "X-Experience-API-Version: " . $request->getHeader('X-Experience-API-Version')[0]; } if ($request->hasHeader('Origin')) { $headers[] = "Origin: " . $request->getHeader('Origin')[0]; @@ -310,12 +310,12 @@ private function sendCurlRequest(string $url, string $authHeader, \Psr\Http\Mess if ($request->hasHeader('Connection')) { $headers[] = "Connection: " . $request->getHeader('Connection')[0]; } -// if ($request->hasHeader('Content-Length')) { -// $contentLength = $request->getHeader('Content-Length'); -// if (is_array($contentLength) && $contentLength[0] != '') { -// $headers[] = "Content-Length: " . $contentLength[0]; -// } -// } + // if ($request->hasHeader('Content-Length')) { + // $contentLength = $request->getHeader('Content-Length'); + // if (is_array($contentLength) && $contentLength[0] != '') { + // $headers[] = "Content-Length: " . $contentLength[0]; + // } + // } if ($request->hasHeader('Content-Type')) { $headers[] = "Content-Type: " . $request->getHeader('Content-Type')[0]; $headers[] = "Accept: application/json"; @@ -358,33 +358,33 @@ private function sendCurlRequest(string $url, string $authHeader, \Psr\Http\Mess $headers['X-Experience-API-Version'] = $request->getHeader('X-Experience-API-Version'); } -// if ($request->hasHeader('Referrer')) { -// $headers['Referrer'] = $request->getHeader('Referrer'); -// } + // if ($request->hasHeader('Referrer')) { + // $headers['Referrer'] = $request->getHeader('Referrer'); + // } -// if ($request->hasHeader('Content-Type')) { -// $headers['Content-Type'] = $request->getHeader('Content-Type'); -// } + // if ($request->hasHeader('Content-Type')) { + // $headers['Content-Type'] = $request->getHeader('Content-Type'); + // } if ($request->hasHeader('Origin')) { -// $headers['Origin'] = $request->getHeader('Origin'); + // $headers['Origin'] = $request->getHeader('Origin'); $headers['Access-Control-Allow-Origin'] = $request->getHeader('Origin'); //$headers["Content-Security-Policy"] = "frame-ancestors 'self' " . $request->getHeader('Origin')[0]; } -// if ($request->hasHeader('Content-Length')) { -// $contentLength = $request->getHeader('Content-Length'); -// if (is_array($contentLength) && $contentLength[0] === '') { -// $contentLength = array(0); -// } elseif ($contentLength === '') { -// $contentLength = array(0); -// } -// $headers['Content-Length'] = $contentLength; -// } - -// if ($request->hasHeader('Connection')) { -// $headers['Connection'] = $request->getHeader('Connection'); -// } + // if ($request->hasHeader('Content-Length')) { + // $contentLength = $request->getHeader('Content-Length'); + // if (is_array($contentLength) && $contentLength[0] === '') { + // $contentLength = array(0); + // } elseif ($contentLength === '') { + // $contentLength = array(0); + // } + // $headers['Content-Length'] = $contentLength; + // } + + // if ($request->hasHeader('Connection')) { + // $headers['Connection'] = $request->getHeader('Connection'); + // }