diff --git a/wcfsetup/install/files/lib/action/ListViewFilterAction.class.php b/wcfsetup/install/files/lib/action/ListViewFilterAction.class.php index 75d9069303b..409ef7ffec2 100644 --- a/wcfsetup/install/files/lib/action/ListViewFilterAction.class.php +++ b/wcfsetup/install/files/lib/action/ListViewFilterAction.class.php @@ -47,7 +47,10 @@ public function handle(ServerRequestInterface $request): ResponseInterface try { /** @var AbstractListView> $view */ - $view = new $parameters['listView'](...$parameters['listViewParameters']); + $view = Helper::mapQueryParametersToClass( + $parameters['listViewParameters'], + $parameters['listView'] + ); // @phpstan-ignore catch.neverThrown } catch (\ArgumentCountError | \TypeError $e) { if (\ENABLE_DEBUG_MODE) { @@ -93,7 +96,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface } return new JsonResponse([ - 'result' => $data + 'result' => $data, ]); } else { throw new \LogicException('Unreachable'); diff --git a/wcfsetup/install/files/lib/http/Helper.class.php b/wcfsetup/install/files/lib/http/Helper.class.php index 732c4bead37..af4551548a0 100644 --- a/wcfsetup/install/files/lib/http/Helper.class.php +++ b/wcfsetup/install/files/lib/http/Helper.class.php @@ -113,11 +113,7 @@ public static function getPreferredContentType(RequestInterface $request, array */ public static function mapQueryParameters(array $queryParameters, string $schema): mixed { - $mapper = (new MapperBuilder()) - ->allowSuperfluousKeys() - ->allowScalarValueCasting() - ->allowUndefinedValues() - ->mapper(); + $mapper = self::getQueryParameterMapperBuilder()->mapper(); return $mapper->map( $schema, @@ -125,6 +121,53 @@ public static function mapQueryParameters(array $queryParameters, string $schema ); } + /** + * Validates query parameters against the constructor signature of the + * provided class and invokes the constructor with the mapped arguments. + * No other properties of the class are mapped. + * + * @template T of object + * @param mixed[] $queryParameters + * @param class-string $className + * @return T + * @throws MappingError + */ + public static function mapQueryParametersToClass(array $queryParameters, string $className): object + { + $reflectionClass = new \ReflectionClass($className); + if (!$reflectionClass->isInstantiable()) { + throw new \InvalidArgumentException("Class '{$className}' is not instantiable."); + } + + $constructor = $reflectionClass->getConstructor(); + if ($constructor === null) { + return $reflectionClass->newInstance(); + } + + $object = $reflectionClass->newInstanceWithoutConstructor(); + $constructorClosure = $constructor->getClosure($object); + \assert($constructorClosure !== null); + + $arguments = self::getQueryParameterMapperBuilder() + ->argumentsMapper() + ->mapArguments( + $constructorClosure, + Source::array($queryParameters) + ); + + $constructorClosure(...$arguments); + + return $object; + } + + private static function getQueryParameterMapperBuilder(): MapperBuilder + { + return (new MapperBuilder()) + ->allowSuperfluousKeys() + ->allowScalarValueCasting() + ->allowUndefinedValues(); + } + /** * Validates body parameters against the provided schema. Expects * the data source to be JSON and thus values to be of the correct diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/listViews/GetItem.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/listViews/GetItem.class.php index 098af503864..ef3ade3d796 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/listViews/GetItem.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/listViews/GetItem.class.php @@ -32,7 +32,10 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res throw new UserInputException('listView', 'invalid'); } - $view = new $parameters->listView(...$parameters->listViewParameters); + $view = Helper::mapQueryParametersToClass( + $parameters->listViewParameters, + $parameters->listView + ); // @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue \assert($view instanceof AbstractListView); @@ -68,5 +71,6 @@ public function __construct( public readonly array $listViewParameters, public readonly bool $allowInteractions = true, public readonly bool $allowBulkInteractions = true, - ) {} + ) { + } } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/listViews/GetItems.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/listViews/GetItems.class.php index 05ab4ab8c2c..c0f5b9bd6b9 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/listViews/GetItems.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/listViews/GetItems.class.php @@ -32,7 +32,10 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res throw new UserInputException('listView', 'invalid'); } - $view = new $parameters->listView(...$parameters->listViewParameters); + $view = Helper::mapQueryParametersToClass( + $parameters->listViewParameters, + $parameters->listView + ); // @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue \assert($view instanceof AbstractListView); @@ -98,5 +101,6 @@ public function __construct( public readonly bool $allowSorting = true, public readonly bool $allowInteractions = true, public readonly bool $allowBulkInteractions = true, - ) {} + ) { + } } diff --git a/wcfsetup/install/files/lib/system/listView/user/TaggedArticleListView.class.php b/wcfsetup/install/files/lib/system/listView/user/TaggedArticleListView.class.php index df506413d9f..603dc1a24b9 100644 --- a/wcfsetup/install/files/lib/system/listView/user/TaggedArticleListView.class.php +++ b/wcfsetup/install/files/lib/system/listView/user/TaggedArticleListView.class.php @@ -16,7 +16,7 @@ class TaggedArticleListView extends ArticleListView { public function __construct( - /** @var list */ + /** @var non-empty-list */ public readonly array $tagIDs, ) { parent::__construct();