external_entities-8.x-2.x-dev/src/Plugin/ExternalEntities/PropertyMapper/PropertyMultiMapping.php
src/Plugin/ExternalEntities/PropertyMapper/PropertyMultiMapping.php
<?php
namespace Drupal\external_entities\Plugin\ExternalEntities\PropertyMapper;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\external_entities\FieldMapper\FieldMapperBase;
use Drupal\external_entities\Form\AjaxFormTrait;
use Drupal\external_entities\PropertyMapper\MultiMapperTrait;
use Drupal\external_entities\PropertyMapper\PropertyMapperBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* This mapper enables to use multiple property mapper for same property.
*
* @PropertyMapper(
* id = "multi",
* label = @Translation("Multiple mapping"),
* description = @Translation("Applies multiple mappings on a same property and allows asymetry between loading and saving."),
* field_properties = {
* "*:*"
* }
* )
*
* @package Drupal\external_entities\Plugin\ExternalEntities\PropertyMapper
*/
class PropertyMultiMapping extends PropertyMapperBase {
use AjaxFormTrait;
use MultiMapperTrait;
/**
* Only aggregate first non-empty mapping.
*/
const AGGREGATE_FIRST = 'first';
/**
* Merge all mappings.
*/
const AGGREGATE_MERGE = 'merge';
/**
* {@inheritdoc}
*/
public function __construct(
array $configuration,
string $plugin_id,
$plugin_definition,
TranslationInterface $string_translation,
LoggerChannelFactoryInterface $logger_factory,
EntityFieldManagerInterface $entity_field_manager,
PluginManagerInterface $data_processor_manager,
PluginManagerInterface $property_mapper_manager,
) {
parent::__construct(
$configuration,
$plugin_id,
$plugin_definition,
$string_translation,
$logger_factory,
$entity_field_manager,
$data_processor_manager
);
$this->setPropertyMapperManager($property_mapper_manager);
}
/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition,
) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('string_translation'),
$container->get('logger.factory'),
$container->get('entity_field.manager'),
$container->get('plugin.manager.external_entities.data_processor'),
$container->get('plugin.manager.external_entities.property_mapper')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'load' => [],
'save' => [],
'aggregation' => static::AGGREGATE_MERGE,
// Do not provide default property mappers as we later use
// NestedArray::mergeDeep() which could lead into invalid
// 'property_mappers' configuration.
'property_mappers' => [],
'required_field' => FALSE,
'main_property' => FALSE,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(
array $form,
FormStateInterface $form_state,
) {
$form += parent::buildConfigurationForm($form, $form_state);
$config = $this->getConfiguration();
unset($form['mapping']);
unset($form['data_processors']);
$property_mappers_count = max(
1,
$form_state->get('pmc_' . $this->getPropertyMapperFormIdentifier($form))
?? count($config['property_mappers'] ?? []),
);
$form_state->set(
'pmc_' . $this->getPropertyMapperFormIdentifier($form),
$property_mappers_count
);
$form['property_mappers'] = [
'#type' => 'container',
// If #parents is not set here, sub-element names will not follow the tree
// structure.
'#parents' => [...($form['#parents'] ?? []), 'property_mappers'],
'#attributes' => [
'id' => $this->getPropertyMapperFormIdentifier($form) . '_mul',
],
];
for ($i = 0; $i < $property_mappers_count; ++$i) {
if (!isset($form['property_mappers'][$i])) {
$form['property_mappers'][$i] = [
'#type' => 'fieldset',
'#title' => $this->t('Mapping "@num"', ['@num' => $i + 1]),
'#parents' => [...($form['#parents'] ?? []), 'property_mappers', $i],
'#attributes' => [
'id' => $this->getPropertyMapperFormIdentifier($form) . '_mul' . $i,
],
'id' => [],
'config' => [],
];
if (1 < $property_mappers_count) {
$form['property_mappers'][$i]['remove_mapper'] = [
'#type' => 'submit',
'#value' => $this->t('Remove this mapping'),
'#name' => 'rempm_' . $this->getPropertyMapperFormIdentifier($form) . '_' . $i,
'#ajax' => [
'callback' => '::buildAjaxParentSubForm',
'wrapper' => $form['property_mappers']['#attributes']['id'],
'method' => 'replaceWith',
'effect' => 'fade',
],
];
}
}
$property_mapper_id = $form_state->getValue(
['property_mappers', $i, 'id'],
$config['property_mappers'][$i]['id']
?? FieldMapperBase::DEFAULT_PROPERTY_MAPPER
);
$this->buildPropertyMapperSelectForm($form, $form_state, $property_mapper_id, $i);
$this->buildPropertyMapperConfigForm($form, $form_state, $property_mapper_id, $i);
}
$form['property_mappers']['add_mapper'] = [
'#type' => 'submit',
'#value' => $this->t('Add another mapping'),
'#name' => 'addpm_' . $this->getPropertyMapperFormIdentifier($form),
'#ajax' => [
'callback' => '::buildAjaxParentSubForm',
'wrapper' => $form['property_mappers']['#attributes']['id'],
'method' => 'replaceWith',
'effect' => 'fade',
],
];
$load_mappers = implode(
',',
array_map(
function ($element) {
return $element + 1;
},
$config['load'] ?? [0]
)
);
$form['load_list'] = [
'#type' => 'textfield',
'#title' => $this->t('Property mappers used for loading'),
'#description' => $this->t('Ordered list of property mapper indexes to use when loading data, separated by commas. Use the numbers of the mappers above.'),
'#default_value' => $load_mappers,
];
$form['aggregation'] = [
'#type' => 'radios',
'#title' => $this->t('How to handle multiple mapping when loading?'),
'#default_value' => $config['aggregation'],
'#options' => [
static::AGGREGATE_FIRST => $this->t('stop at first non-empty mapping'),
static::AGGREGATE_MERGE => $this->t('merge all mappings'),
],
];
$save_mappers = implode(
',',
array_map(
function ($element) {
return $element + 1;
},
$config['save'] ?? []
)
);
$form['save_list'] = [
'#type' => 'textfield',
'#title' => $this->t('Property mappers used for saving'),
'#description' => $this->t('Ordered list of property mapper indexes to use when saving data, separated by commas. Use the numbers of the mappers above.'),
'#default_value' => $save_mappers,
];
$form['#attached']['library'][] = 'external_entities/external_entities';
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
// Check for Ajax events.
$pmc_count_prop = 'pmc_' . $this->getPropertyMapperFormIdentifier($form);
$property_mappers_count = $form_state->get($pmc_count_prop);
if ($trigger = $form_state->getTriggeringElement()) {
$pm_id = $form['#attributes']['id'] ?? '';
if ("addpm_$pm_id" == $trigger['#name']) {
++$property_mappers_count;
$form_state->set($pmc_count_prop, $property_mappers_count);
$form_state->setRebuild(TRUE);
}
elseif (preg_match("#^rempm_\\Q$pm_id\\E_(\\d+)\$#", $trigger['#name'], $matches)) {
$mapper_number = $matches[1];
if (isset($mapper_number)) {
--$property_mappers_count;
$form_state->set($pmc_count_prop, $property_mappers_count);
// Reorder property mapper configs.
$user_input = $form_state->getUserInput();
$property_mappers = NestedArray::getValue(
$user_input,
$form['property_mappers']['#parents'] ?? []
);
for ($config_number = $mapper_number; $config_number < $property_mappers_count; ++$config_number) {
// Shift form values.
$form_state->setValueForElement(
$form['property_mappers'][$config_number],
$form_state->getValue(
['property_mappers', $config_number + 1]
)
);
$property_mappers[$config_number] =
$property_mappers[$config_number + 1];
}
NestedArray::setValue(
$user_input,
$form['property_mappers']['#parents'] ?? [],
$property_mappers
);
$form_state->setUserInput($user_input);
$form_state->setRebuild(TRUE);
}
}
}
parent::validateConfigurationForm($form, $form_state);
for ($i = 0; $i < $property_mappers_count; ++$i) {
$this->validatePropertyMapperConfigurationForm($form, $form_state, $i);
}
$load_mappers = [];
$load_list = explode(',', $form_state->getValue('load_list', ''));
foreach ($load_list as $load_mapper) {
$value = trim($load_mapper);
if (!is_numeric($value)
|| ($value < 1)
|| ($value > $property_mappers_count)
) {
$form_state->setErrorByName(
'load_list',
$this->t(
'Invalid property mapper index "@index" in load list.',
['@index' => $load_mapper]
)
);
continue;
}
$load_mappers[] = $value - 1;
}
$form_state->setValue('load', $load_mappers);
$save_mappers = [];
$save_list = explode(',', $form_state->getValue('save_list', ''));
foreach ($save_list as $save_mapper) {
$value = trim($save_mapper);
if (!is_numeric($value)
|| ($value < 1)
|| ($value > $property_mappers_count)
) {
$form_state->setErrorByName(
'save_list',
$this->t(
'Invalid property mapper index "@index" in save list.',
['@index' => $save_mapper]
)
);
continue;
}
$save_mappers[] = $value - 1;
}
$form_state->setValue('save', $save_mappers);
// If rebuild needed, ignore validation.
if ($form_state->isRebuilding()) {
$form_state->clearErrors();
}
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$property_mappers = $form_state->getValue('property_mappers', []);
unset($property_mappers['add_mapper']);
$form_state->setValue('property_mappers', $property_mappers);
$property_mappers_count = $form_state->get('pmc_' . $this->getPropertyMapperFormIdentifier($form));
for ($i = 0; $i < $property_mappers_count; ++$i) {
$this->submitPropertyMapperConfigurationForm($form, $form_state, $i);
}
$form_state->unsetValue('load_list');
$form_state->unsetValue('save_list');
$form_state->unsetValue('data_processors');
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function addPropertyValuesToRawData(
array $property_values,
array &$raw_data,
array &$context,
) {
$config = $this->getConfiguration();
// If saving is disabled, stop here.
if (empty($config['save'])) {
return;
}
foreach ($config['save'] as $index) {
if (empty($config['property_mappers'][$index]['id'])) {
// Invalid index, skip it.
continue;
}
$property_mapper = $this->getPropertyMapper($index);
$property_mapper->addPropertyValuesToRawData(
$property_values,
$raw_data,
$context
);
}
}
/**
* {@inheritdoc}
*/
public function getMappedSourceFieldName() :?string {
if ($this->isProcessed()) {
return NULL;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function extractPropertyValuesFromRawData(
array $raw_data,
array &$context = [],
) :array {
$config = $this->getConfiguration();
// If loading is disabled, stop here.
if (empty($config['load'])) {
return [];
}
$result_data = [];
foreach ($config['load'] as $index) {
if (empty($config['property_mappers'][$index]['id'])) {
// Invalid index, skip it.
continue;
}
$property_mapper = $this->getPropertyMapper($index);
if (static::AGGREGATE_MERGE === $config['aggregation']) {
$result_data = NestedArray::mergeDeep(
$result_data,
$property_mapper->extractPropertyValuesFromRawData(
$raw_data,
$context
)
);
}
elseif (static::AGGREGATE_FIRST === $config['aggregation']) {
$result_data = $property_mapper->extractPropertyValuesFromRawData(
$raw_data,
$context
);
foreach ($result_data ?? [] as $value) {
if (!empty($value) || is_numeric($value)) {
break 2;
}
}
}
}
return $result_data;
}
}
