diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 7b475113af5..4c9a6a71e76 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -316,6 +316,7 @@ - `craft\services\Users::unshunMessageForUser()` now has a `void` return type, and throws an `InvalidElementException` in case of failure. - `craft\services\Users::unsuspendUser()` now has a `void` return type, and throws an `InvalidElementException` in case of failure. - `craft\services\Users::verifyEmailForUser()` now has a `void` return type, and throws an `InvalidElementException` in case of failure. +- `craft\web\View::setNamespace()` now throws an `InvalidArgumentException` for namespaces that don’t confirm to HTML `id` attribute rules (possibly followed by sets of properly-formatted strings wrapped in square brackets). ([#13943](https://github.com/craftcms/cms/issues/13943)) - Deprecated the `_elements/element.twig` control panel template. `elementChip()` or `elementCard()` should be used instead. - Deprecated the `cp.elements.element` control panel template hook. - Deprecated `craft\events\DefineElementInnerHtmlEvent`. diff --git a/src/web/View.php b/src/web/View.php index 7ee61e237f4..919a66cf8ab 100644 --- a/src/web/View.php +++ b/src/web/View.php @@ -38,6 +38,7 @@ use Twig\TemplateWrapper; use yii\base\Arrayable; use yii\base\Exception; +use yii\base\InvalidArgumentException; use yii\base\Model; use yii\base\NotSupportedException; use yii\web\AssetBundle as YiiAssetBundle; @@ -1438,6 +1439,13 @@ public function getNamespace(): ?string */ public function setNamespace(?string $namespace): void { + if ( + $namespace !== null && + !preg_match('/^[A-Za-z][A-Za-z0-9\-_:.]*(\[[A-Za-z][A-Za-z0-9\-_:.]*])*$/', $namespace) + ) { + throw new InvalidArgumentException(sprintf('Invalid namespace ("%s"). Namespaces must begin with a letter, and may be followed by letters, numbers, hyphens, underscores, colons, and periods (possibly followed by sets of correctly-formatted strings wrapped in square brackets).', $namespace)); + } + $this->_namespace = $namespace; } diff --git a/tests/unit/web/ViewTest.php b/tests/unit/web/ViewTest.php index 7c95720f220..d28faf4c918 100644 --- a/tests/unit/web/ViewTest.php +++ b/tests/unit/web/ViewTest.php @@ -25,6 +25,7 @@ use UnitTester; use yii\base\Event; use yii\base\Exception; +use yii\base\InvalidArgumentException; /** * Unit tests for the View class @@ -307,6 +308,26 @@ public function testNamespaceInputId(string $expected, string $string, ?string $ self::assertSame($expected, $this->view->namespaceInputId($string, $namespace)); } + /** + * @dataProvider setNamespaceDataProvider + * @param string|null $namespace + * @param bool $isValid + */ + public function testSetNamespace(?string $namespace, bool $isValid): void + { + $oldNamespace = $this->view->getNamespace(); + + if (!$isValid) { + self::expectException(InvalidArgumentException::class); + } + + $this->view->setNamespace($namespace); + self::assertEquals($namespace, $this->view->getNamespace()); + + $this->view->setNamespace($oldNamespace); + self::assertEquals($oldNamespace, $this->view->getNamespace()); + } + /** * @dataProvider getTemplateRootsDataProvider * @param array $expected @@ -602,6 +623,31 @@ public static function namespaceInputIdDataProvider(): array ]; } + /** + * @return array + */ + public static function setNamespaceDataProvider(): array + { + return [ + [null, true], + ['foo', true], + ['foo[bar]', true], + ['foo[bar][baz]', true], + ['foo[bar0:baz.1-_]', true], + ['', false], + ['0', false], + ['1', false], + ['foo[]', false], + ['foo[0]', false], + ['foo[1]', false], + ['foo[bar][]', false], + ['foo[bar][0]', false], + ['foo[bar][1]', false], + ['foo[bar', false], + [' foo', false], + ]; + } + /** * @return array */