Skip to content
Snippets Groups Projects

Resolve #3512477 "2.0.2 improve the"

Files
3
@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace Drupal\ui_patterns\Element;
use Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Plugin\Component;
use Drupal\Core\Render\Element;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\ui_patterns\ComponentPluginManager as UiPatternsComponentPluginManager;
use Drupal\ui_patterns\PropTypePluginManager;
use Drupal\ui_patterns\SourceInterface;
use Drupal\ui_patterns\PropTypeInterface;
use Drupal\ui_patterns\SourcePluginBase;
use Drupal\ui_patterns\SourcePluginManager;
use Psr\Log\LoggerInterface;
@@ -34,7 +34,6 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
*/
public function __construct(
protected SourcePluginManager $sourcesManager,
protected PropTypePluginManager $propTypeManager,
protected ComponentPluginManager $componentPluginManager,
protected ModuleHandlerInterface $moduleHandler,
protected LoggerInterface $logger,
@@ -79,75 +78,86 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
* Add a single prop to the renderable.
*/
protected function buildProp(array $build, string $prop_id, array $definition, array $configuration, array $source_contexts): array {
if (isset($build["#props"][$prop_id])) {
if (isset($build['#props'][$prop_id])) {
// Keep existing props. No known use case yet.
return $build;
}
$source = $this->getSource($prop_id, $definition, $configuration, $source_contexts);
if (!$source) {
return $build;
return $this->buildSource($build, $prop_id, $definition, $configuration, $source_contexts);
}
/**
* Add data to a prop or a slot.
*/
protected function addDataToComponent(array &$build, string $prop_or_slot_id, PropTypeInterface $prop_type, mixed $data): void {
if ($prop_type instanceof SlotPropType) {
if ($data !== NULL && Element::isRenderArray($data)) {
if ($this->isSingletonRenderArray($data)) {
$data = array_values($data)[0];
}
$build['#slots'][$prop_or_slot_id][] = $data;
}
}
try {
$build = $source->alterComponent($build);
$prop_type = $definition['ui_patterns']['type_definition'];
$data = $source->getValue($prop_type);
$this->moduleHandler->alter('ui_patterns_source_value', $data, $source, $configuration);
if (empty($data) && $prop_type->getPluginId() !== 'attributes') {
else {
if (!empty($data) || $prop_type->getPluginId() === 'attributes') {
// For JSON Schema validator, empty value is not the same as missing
// value, and we want to prevent some of the prop types rules to be
// applied on empty values: string pattern, string format, enum, number
// min/max...
// applied on empty values: string pattern, string format,
// enum, number min/max...
// However, we don't remove empty attributes to avoid an error with
// Drupal\Core\Template\TwigExtension::createAttribute() when themers
// forget to use the default({}) filter.
return $build;
$build['#props'][$prop_or_slot_id] = $data;
}
$build["#props"][$prop_id] = $data;
}
catch (ContextException $e) {
// ContextException is thrown when a required context is missing.
// We don't want to break the render process, so we just ignore the prop.
$error_message = t("Context error for prop '@prop_id' in component '@component_id': @message", [
'@prop_id' => $prop_id,
'@component_id' => $build['#component'],
'@message' => $e->getMessage(),
]);
$this->logger->error($error_message);
}
return $build;
}
/**
* Get Source plugin for a prop.
* Update the build array for a configured source on a prop/slot.
*
* @param array $build
* The build array.
* @param string $prop_or_slot_id
* Prop ID or slot ID.
* @param array $definition
* Definition.
* @param array $configuration
* Configuration.
* @param array $source_contexts
* @param array $contexts
* Source contexts.
*
* @return \Drupal\ui_patterns\SourceInterface|null
* The source found or NULL.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* @return mixed
* The updated build array.
*/
protected function getSource(string $prop_or_slot_id, array $definition, array $configuration, array $source_contexts) : ?SourceInterface {
$source_id = $configuration["source_id"] ?? NULL;
if (!$source_id && isset($definition['ui_patterns']['type_definition'])) {
$source_id = $this->sourcesManager->getPropTypeDefault($definition['ui_patterns']['type_definition']->getPluginId(), $source_contexts);
public function buildSource(array $build, string $prop_or_slot_id, array $definition, array $configuration, array $contexts) : mixed {
try {
if (empty($configuration['source_id'])) {
return $build;
}
$source = $this->sourcesManager->getSource($prop_or_slot_id, $definition, $configuration, $contexts);
if (!$source) {
return $build;
}
/** @var \Drupal\ui_patterns\PropTypeInterface $prop_type */
$prop_type = $source->getPropDefinition()['ui_patterns']['type_definition'];
// Alter the build array before getting the value.
$build = $source->alterComponent($build);
// Get the value from the source.
$data = $source->getValue($prop_type);
// Alter the value by hook implementations.
$this->moduleHandler->alter('ui_patterns_source_value', $data, $source, $configuration);
$this->addDataToComponent($build, $prop_or_slot_id, $prop_type, $data);
}
if (!$source_id) {
return NULL;
catch (ContextException $e) {
// ContextException is thrown when a required context is missing.
// We don't want to break the render process, so we just ignore the prop.
$error_message = t("Context error for '@prop_id' in component '@component_id': @message", [
'@prop_id' => $prop_or_slot_id,
'@component_id' => $build['#component'],
'@message' => $e->getMessage(),
]);
$this->logger->error($error_message);
}
/** @var \Drupal\ui_patterns\SourceInterface $source */
$source = $this->sourcesManager->createInstance(
$source_id,
SourcePluginBase::buildConfiguration($prop_or_slot_id, $definition, $configuration, $source_contexts)
);
return $source;
return $build;
}
/**
@@ -167,34 +177,22 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
* Add a single slot to the renderable.
*/
protected function buildSlot(array $build, string $slot_id, array $definition, array $configuration, array $contexts): array {
if (isset($build["#slots"][$slot_id])) {
if (isset($build['#slots'][$slot_id])) {
// Keep existing slots. Used by ComponentLayout for example.
return $build;
}
if (!isset($configuration["sources"])) {
if (!isset($configuration['sources'])) {
return $build;
}
// Slots can have many sources while props can have only one.
$build["#slots"][$slot_id] = [];
/** @var \Drupal\ui_patterns\PropTypeInterface $slot_prop_type */
$slot_prop_type = $this->propTypeManager->createInstance("slot", []);
$build['#slots'][$slot_id] = [];
// Add sources data to the slot.
foreach ($configuration["sources"] as $source_configuration) {
$source = $this->getSource($slot_id, $definition, $source_configuration, $contexts);
if (!$source) {
continue;
}
$build = $source->alterComponent($build);
$source_value = $source->getValue($slot_prop_type) ?? [];
$this->moduleHandler->alter('ui_patterns_source_value', $source_value, $source, $source_configuration);
if (Element::isRenderArray($source_value)) {
$build["#slots"][$slot_id][] = $this->isSingletonRenderArray($source_value) ? array_values($source_value)[0] : $source_value;
}
foreach ($configuration['sources'] as $source_configuration) {
$build = $this->buildSource($build, $slot_id, $definition, $source_configuration, $contexts);
}
if ($this->isSingletonRenderArray($build["#slots"][$slot_id])) {
$build["#slots"][$slot_id] = $build["#slots"][$slot_id][0];
if ($this->isSingletonRenderArray($build['#slots'][$slot_id])) {
$build['#slots'][$slot_id] = $build['#slots'][$slot_id][0];
}
return $build;
}
@@ -269,7 +267,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
if ($prop_id === 'variant') {
continue;
}
if ($source = $this->getSource($prop_id, $definition, $configuration['props'][$prop_id] ?? [], $contexts)) {
if ($source = $this->sourcesManager->getSource($prop_id, $definition, $configuration['props'][$prop_id] ?? [], $contexts)) {
SourcePluginBase::mergeConfigDependencies($dependencies, $source->calculateDependencies());
}
}
@@ -294,11 +292,11 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
$slots = $component->metadata->slots ?? [];
foreach ($slots as $slot_id => $definition) {
$slot_configuration = $configuration['slots'][$slot_id] ?? [];
if (!isset($slot_configuration["sources"]) || !is_array($slot_configuration["sources"])) {
if (!isset($slot_configuration['sources']) || !is_array($slot_configuration['sources'])) {
continue;
}
foreach ($slot_configuration["sources"] as $source_configuration) {
if ($source = $this->getSource($slot_id, $definition, $source_configuration, $contexts)) {
foreach ($slot_configuration['sources'] as $source_configuration) {
if ($source = $this->sourcesManager->getSource($slot_id, $definition, $source_configuration, $contexts)) {
SourcePluginBase::mergeConfigDependencies($dependencies, $source->calculateDependencies());
}
}
Loading