Skip to content
Snippets Groups Projects
Commit 6098bb55 authored by kensae's avatar kensae
Browse files

Merge branch '3515500-prevent-prop-s' into '2.0.x'

Prevent props keys from being prefixed with #

See merge request !360
parents 0d3fa144 b4d78ef0
No related branches found
No related tags found
No related merge requests found
Pipeline #474706 passed
......@@ -11,88 +11,224 @@ namespace Drupal\ui_patterns_library;
* Let's put them back.
*
* Before: ["type" => "component", "component" => "example:card"]
* After: ["#type" => "component", "#component" => "example:card"]
* After: ["#type" => "component", "#component" => "example:card"]
*/
class StoriesSyntaxConverter {
/**
* An array with one (and only one) of those keys may be a render array.
*/
const RENDER_KEYS = ["theme", "type", "markup", "plain_text", "#theme", "#type", "#markup", "#plain_text"];
public const RENDER_KEYS = [
'markup',
'plain_text',
'theme',
'type',
'#markup',
'#plain_text',
'#theme',
'#type',
];
public const KNOWN_PROPERTIES = [
'type' => [
'html_tag' => [
'attached',
'attributes',
'tag',
'type',
'value',
],
],
'theme' => [
'layout' => [
'attached',
'attributes',
'theme',
'settings',
],
],
];
/**
* Process stories slots.
* List of render properties which should have been children instead.
*/
public function convertSlots(array $slots): array {
foreach ($slots as $slot_id => $slot) {
if (!is_array($slot)) {
continue;
}
$slots[$slot_id] = $this->convertArray($slot);
}
return $slots;
}
public const PROPERTIES_INSTEAD_OF_CHILDREN = [
'type' => [
'component' => [
'slots',
],
],
'theme' => [
'status_messages' => [
'message_list',
],
'table' => [
'header',
'rows',
'footer',
'empty',
'caption',
],
],
];
/**
* Convert an array.
* Process stories slots.
*/
protected function convertArray(array $array): array {
public function convertSlots(array $array): array {
if ($this->isRenderArray($array)) {
return $this->convertRenderArray($array);
}
foreach ($array as $index => $value) {
if (!is_array($value)) {
if (!\is_array($value)) {
continue;
}
$array[$index] = $this->convertArray($value);
$array[$index] = $this->convertSlots($value);
}
return $array;
}
/**
* Convert a render array.
*
* @param array $renderable
* The render array being processed.
*
* @return array
* The processed render array.
*/
protected function convertRenderArray(array $renderable): array {
foreach ($renderable as $property => $value) {
if (is_array($value)) {
$renderable[$property] = $this->convertArray($value);
// Weird detection.
if (isset($renderable['type'], self::PROPERTIES_INSTEAD_OF_CHILDREN['type'][$renderable['type']])) {
return $this->convertWeirdRenderArray($renderable, self::PROPERTIES_INSTEAD_OF_CHILDREN['type'][$renderable['type']]);
}
if (isset($renderable['theme'])) {
$baseThemeHook = \explode('__', $renderable['theme'])[0];
if (isset(self::PROPERTIES_INSTEAD_OF_CHILDREN['theme'][$baseThemeHook])) {
return $this->convertWeirdRenderArray($renderable, self::PROPERTIES_INSTEAD_OF_CHILDREN['theme'][$baseThemeHook]);
}
}
$in_html_tag = (isset($renderable["type"]) && $renderable["type"] === "html_tag");
$html_tag_allowed_render_keys = ["type", "attributes", "tag", "value", "attached"];
// Normal with special case detection.
if (isset($renderable['type'], self::KNOWN_PROPERTIES['type'][$renderable['type']])) {
return $this->convertNormalRenderArray($renderable, self::KNOWN_PROPERTIES['type'][$renderable['type']]);
}
if (isset($renderable['theme'])) {
$baseThemeHook = \explode('__', $renderable['theme'])[0];
if (isset(self::KNOWN_PROPERTIES['theme'][$baseThemeHook])) {
return $this->convertNormalRenderArray($renderable, self::KNOWN_PROPERTIES['theme'][$baseThemeHook]);
}
}
return $this->convertNormalRenderArray($renderable, []);
}
/**
* Add property prefix.
*
* @param array $renderable
* The renderable array.
* @param mixed $property
* The property.
*
* @return array
* The array with prefixed property.
*/
protected function convertProperty(array $renderable, mixed $property): array {
if (!\is_string($property)) {
return $renderable;
}
if (\str_starts_with($property, '#')) {
return $renderable;
}
$renderable['#' . $property] = $renderable[$property];
unset($renderable[$property]);
return $renderable;
}
/**
* To convert "normal" render array.
*
* A "normal" render arrays is an array:
* - where properties (key starts with a '#') are not renderables
* - children (key does not start with a '#') are only renderables.
*
* Examples:
* - html_tag which is forbidding renderables in #value
* - layout where every region is a child.
*
* @param array $renderable
* The renderable array.
* @param array $knownProperties
* The list of know properties.
*
* @return array
* The converted array.
*/
protected function convertNormalRenderArray(array $renderable, array $knownProperties): array {
foreach ($renderable as $property => $value) {
if (!is_string($property)) {
continue;
if (empty($knownProperties) && \is_string($property)) {
// Default to add prefix to every entries.
$renderable = $this->convertProperty($renderable, $property);
}
// html_tag is special.
if ($in_html_tag && !in_array($property, $html_tag_allowed_render_keys)) {
continue;
elseif (\in_array($property, $knownProperties, TRUE)) {
// We add # prefix only to known properties.
$renderable = $this->convertProperty($renderable, $property);
}
if (str_starts_with($property, "#")) {
continue;
elseif (\is_array($value)) {
// Other keys may have children so let's drill.
$renderable[$property] = $this->convertSlots($value);
}
}
return $renderable;
}
/**
* The "weird" render arrays, where renderables are found only in properties.
*
* Examples: component with #slots, table with #rows...
*
* @param array $renderable
* The renderable array.
* @param array $propertiesWithRenderables
* The list of properties to look for.
*
* @return array
* The updated renderable array.
*/
protected function convertWeirdRenderArray(array $renderable, array $propertiesWithRenderables): array {
foreach ($renderable as $property => $value) {
if (\in_array($property, $propertiesWithRenderables, TRUE) && \is_array($value)) {
$renderable[$property] = $this->convertSlots($value);
}
$renderable["#" . $property] = $value;
unset($renderable[$property]);
// There are no children, so we add a # everywhere.
$renderable = $this->convertProperty($renderable, $property);
}
return $renderable;
}
/**
* Is the array a render array?
*
* @param array $array
* The array being processed.
*
* @return bool
* True if a render array.
*/
protected function isRenderArray(array $array): bool {
if (array_is_list($array)) {
if (\array_is_list($array)) {
return FALSE;
}
// An array needs one, and only one, of those properties to be a render
// array.
$intersect = array_intersect(array_keys($array), self::RENDER_KEYS);
if (count($intersect) != 1) {
$intersect = \array_intersect(\array_keys($array), self::RENDER_KEYS);
if (\count($intersect) != 1) {
return FALSE;
}
// This property has to be a string value.
$property = $intersect[array_key_first($intersect)];
if (!is_string($array[$property])) {
$property = $intersect[\array_key_first($intersect)];
if (!\is_string($array[$property])) {
return FALSE;
}
return TRUE;
......
......@@ -8,9 +8,10 @@ use Drupal\Tests\UnitTestCase;
use Drupal\ui_patterns_library\StoriesSyntaxConverter;
/**
* @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\LinksPropType
* @coversDefaultClass \Drupal\ui_patterns_library\StoriesSyntaxConverter
*
* @group ui_patterns
* @group ui_patterns_library
*/
final class StoriesSyntaxConversionTest extends UnitTestCase {
......@@ -22,7 +23,7 @@ final class StoriesSyntaxConversionTest extends UnitTestCase {
public function testConvertSlot(array $value, array $expected): void {
$converter = new StoriesSyntaxConverter();
$converted = $converter->convertSlots($value);
self::assertEquals($converted, $expected);
self::assertEquals($expected, $converted);
}
/**
......@@ -34,6 +35,7 @@ final class StoriesSyntaxConversionTest extends UnitTestCase {
"Bootstrap accordion" => self::bootstrapAccordion(),
"Bootstrap carousel" => self::bootstrapCarousel(),
"Daisy Grid Row 4" => self::daisyGridRow4(),
"Props special words" => self::componentSpecialProps(),
];
foreach ($data as $label => $test) {
yield $label => [
......@@ -296,4 +298,48 @@ final class StoriesSyntaxConversionTest extends UnitTestCase {
];
}
/**
* Fake component with props with reserved words.
*/
protected static function componentSpecialProps(): array {
$slots = [
'component' => [
'type' => 'component',
'component' => 'foo:bar',
'props' => [
'attributes' => [
'class' => ['bar'],
'type' => 'bar',
],
'tag' => 'bar',
'type' => 'bar',
'value' => 'bar',
'attached' => 'bar',
],
],
];
$expected = [
'component' => [
'#type' => 'component',
'#component' => 'foo:bar',
'#props' => [
'attributes' => [
'class' => [
0 => 'bar',
],
'type' => 'bar',
],
'tag' => 'bar',
'type' => 'bar',
'value' => 'bar',
'attached' => 'bar',
],
],
];
return [
'value' => $slots,
'expected' => $expected,
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment