visualn-8.x-1.x-dev/src/Helpers/VisualNFormsHelper.php
src/Helpers/VisualNFormsHelper.php
<?php
namespace Drupal\visualn\Helpers;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Render\Element;
use Drupal\Component\Utility\NestedArray;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
class VisualNFormsHelper {
// @todo: rename to doProcessDrawerContainerSubform() to avoid confusion
public static function processDrawerContainerSubform(array $element, FormStateInterface $form_state, $form, $configuration) {
$visualNStyleStorage = \Drupal::service('entity_type.manager')->getStorage('visualn_style');
// @todo: how to check if the form is fresh
// is null basically means that the form is fresh (maybe check the whole $form_state->getValues() to be sure?)
$style_element_parents = array_slice($element['#parents'], 0, -1);
// since the function if called as a #process callback and the visualn_style_id select was already processed
// and the values were mapped then it is enough to get form_state value for it and no need to check
// configuration value (see FormBuilder::processForm() and FormBuilder::doBuildForm())
// and no need in "is_null($visualn_style_id) then set value from config"
$visualn_style_id = $form_state->getValue(array_merge($style_element_parents, ['visualn_style_id']));
// If it is a fresh form (is_null($visualn_style_id)) or an empty option selected ($visualn_style_id == ""),
// there is nothing to attach for drawer config.
if (!$visualn_style_id) {
return $element;
}
// Here the drawer plugin is initialized (inside getDrawerPlugin()) with the config stored in the style.
$drawer_plugin = $visualNStyleStorage->load($visualn_style_id)->getDrawerPlugin();
// We use drawer config from configuration only if it corresponds to the selected style. Also
// we don't get form_state values for the drawer config here since they are handled by
// drawer buildConfigurationForm() method itself and also even in buildConfigurationForm()
// drawer should have access to the $this->configuration['drawer_config'] values.
if ($visualn_style_id == $configuration['visualn_style_id']) {
// Set initial configuration for the plugin according to the configuration stored in fetcher config.
$drawer_config = $configuration['drawer_config'];
$drawer_plugin->setConfiguration($drawer_config);
// @todo: uncomment when the issue with handling drawer fields form_state values is resolved.
//$drawer_fields = $this->configuration['drawer_fields'];
// @todo: Until some generic way to hande drawer_fields form is introduced,
// e.g. \VisualN::buildDrawerDataKeysForm(), we should handle form_state values for the drawer_fields
// manually (i.e. in case of form validation errors form_state values should be used).
$drawer_fields
= $form_state->getValue(array_merge($style_element_parents, ['drawer_fields']), $configuration['drawer_fields']);
// @todo: Technically values should be taken from array_merge($element['#parents'], ['drawer_fields'])
// but since validation function (see #element_validate) restructures the values (which should be
// done at submit level), we're taking it into consideration here.
}
else {
// Leave drawer_config unset for later initialization with drawer_plugin->getConfiguration() values
// which are generally taken from visualn style configuration.
// Initialize drawer_config based on (visualn style stored config) in case it is needed somewhere else below.
$drawer_config = $drawer_plugin->getConfiguration();
// Since drawer_fields is always an empty array for a visualn style drawer plugin (VisualNStyle::getDrawerPlugin()),
// it is ok to set it to an empty array here. In contrast, if null, drawer_config should be taken
// from the visualn style plugin configuraion.
$drawer_fields = [];
}
// Attach drawer configuration form
// The visualn style stored drawer configuration is generally only used for fresh form or when
// switching visualn select box to a new style. In other cases drawer_config is provided by
// the fetcher_config or set to an empty array.
// Remember that this drawer_config is used only for plugin initialization, the build config form method
// also checks form_state by itself.
// Use unique drawer container key for each visualn style from the select box so that the settings
// wouldn't be overridden by the previous one on ajax calls (expecially when styles use the same
// drawer and thus the same configuration form with the same keys).
$drawer_container_key = $visualn_style_id;
// get drawer configuration form
$element[$drawer_container_key]['drawer_config'] = [];
$element[$drawer_container_key]['drawer_config'] += [
'#parents' => array_merge($element['#parents'], [$drawer_container_key, 'drawer_config']),
'#array_parents' => array_merge($element['#array_parents'], [$drawer_container_key, 'drawer_config']),
];
$subform_state = SubformState::createForSubform($element[$drawer_container_key]['drawer_config'], $form, $form_state);
// attach drawer configuration form
$element[$drawer_container_key]['drawer_config']
= $drawer_plugin->buildConfigurationForm($element[$drawer_container_key]['drawer_config'], $subform_state);
// @todo: Use some kind of \VisualN::buildDrawerDataKeysForm($drawer_plugin, $form, $form_state) here.
// @todo: trim data_keys values after submitting settings
// Drawer fields subform (i.e. data_keys mappings) should be attached in a separate #process callback
// that would trigger after the drawer buildConfigurationForm() attaches the config form
// and is completed. It is required for the case when drawer has a variable number of data keys.
// see the code below
// @see VisualNDrawing::processDrawerContainerSubform()
$element[$drawer_container_key]['drawer_fields']['#process'] = [[get_called_class(), 'processDrawerFieldsSubform']];
$element[$drawer_container_key]['drawer_fields']['#drawer_plugin'] = $drawer_plugin;
$element[$drawer_container_key]['drawer_fields']['#drawer_fields'] = $drawer_fields;
// Check if drawer config has ajaxified elements to update data keys (same as in views).
// This should be done in #process (but not #after_build) since #ajax settings use
// also a #process callback to attach js etc.
$style_element_array_parents = array_slice($element['#array_parents'], 0, -1);
$base_element = NestedArray::getValue($form, $style_element_array_parents);
// get ajax wrapper id to replace the original drawer one if found
$ajax_wrapper_id = $base_element['visualn_style_id']['#ajax']['wrapper'];
static::replaceAjaxOptions($element, $form_state, $ajax_wrapper_id);
// open drawer_container 'details' if needed
$element[$drawer_container_key]['#after_build'][] = [get_called_class(), 'afterBuildDrawerDetailsOpenSubform'];
// @todo: replace with #element_submit when introduced into core
// extract values for drawer_container subform and drawer_config and drawer_fields
// remove drawer_container key from form_state values path
// also it can be done in ::submitConfigurationForm()
$element[$drawer_container_key]['#element_validate'] = [[get_called_class(), 'validateDrawerContainerSubForm']];
//$element[$drawer_container_key]['#element_validate'] = [[get_called_class(), 'submitDrawerContainerSubForm']];
return $element;
}
/**
* Attach drawer_fields subform based on drawer_plugin dataKeys().
*
* The subform is attached in a #process callback to have drawer config form
* values already mapped at this point. It is needed e.g. for drawers with variable
* number of data keys managed in a #process callback set in buildConfigurationForm().
*
* @see \Drupal\visualn_basic_drawers\Plugin\VisualN\Drawer\LinechartBasicDrawer
*/
public static function processDrawerFieldsSubform(array $element, FormStateInterface $form_state, $form) {
$element_parents = $element['#array_parents'];
$base_element_parents = array_slice($element_parents, 0, -1);
$base_element_parents[] = 'drawer_config';
$config_element = NestedArray::getValue($form, $base_element_parents);
$subform_state = SubformState::createForSubform($config_element, $form, $form_state);
$drawer_plugin = $element['#drawer_plugin'];
$drawer_fields = $element['#drawer_fields'];
$drawer_plugin_clone = clone $drawer_plugin;
$drawer_config = $drawer_plugin->extractFormValues($config_element, $subform_state);
$drawer_plugin_clone->setConfiguration($drawer_config);
$data_keys = $drawer_plugin_clone->dataKeys();
// @todo: convert textfields into a table in a #process callback
// maybe even inside Mapper config form method
if (!empty($data_keys)) {
// @todo: get rid of value from 'field' or massage value at plugin submit
$element += [
'#type' => 'table',
'#header' => [t('Data key'), t('Field')],
];
foreach ($data_keys as $i => $data_key) {
$element[$data_key]['label'] = [
'#plain_text' => $data_key,
];
$element[$data_key]['field'] = [
'#type' => 'textfield',
'#default_value' => isset($drawer_fields[$data_key]) ? $drawer_fields[$data_key] : '',
];
}
}
return $element;
}
/**
* Process callback for drawer config and drawer fields container subform.
*
* Check if 'details' element should be open after all drawer_config and drawer_fields elements are attached.
*/
public static function afterBuildDrawerDetailsOpenSubform(array $element, FormStateInterface $form_state) {
// since drawer and fields configuration forms may be empty, do a check (then it shouldn't be of details type)
if (Element::children($element['drawer_config']) || Element::children($element['drawer_fields'])) {
// @todo: actually it is base_element_array_parents for both, visualn_style_id and drawer_config
$style_element_array_parents = array_slice($element['#array_parents'], 0, -2);
//$style_element_array_parents = array_slice($element['#array_parents'], 0, -1);
// check that the triggering element is visualn_style_id but not fetcher_id select (or some other element) itself
$details_open = FALSE;
if ($form_state->getTriggeringElement()) {
$triggering_element = $form_state->getTriggeringElement();
$details_open = $triggering_element['#array_parents'] === array_merge($style_element_array_parents, ['visualn_style_id']);
// also open details on ajaxified element click
if (!$details_open) {
$diff = array_diff($triggering_element['#array_parents'], $style_element_array_parents);
// the $diff is supposed not to be empty since drawer_config and visualn_style_id have the same base
if ($diff) {
$details_open = $triggering_element['#array_parents'] === array_merge($style_element_array_parents, $diff);
}
}
}
$element = [
'#type' => 'details',
'#title' => t('Style configuration'),
'#open' => $details_open,
] + $element;
}
return $element;
}
// @todo: Restructuring form_state values (removing drawer_container key) should be moved
// into #element_submit callback when introduced.
public static function validateDrawerContainerSubForm(&$form, FormStateInterface $form_state, $full_form) {
// @todo: the code here should actually go to #element_submit, but it is not implemented at the moment in Drupal core
// Here the full form_state (e.g. not SubformStateInterface) is supposed to be
// since validation is done after the whole form is rendered.
// get drawer_container_key (for selected visualn style is equal by convention to visualn_style_id,
// see processDrawerContainerSubform() #process callback)
$element_parents = $form['#parents'];
// use $drawer_container_key for clarity though may get rid of array_pop() here and use end($element_parents)
$drawer_container_key = array_pop($element_parents);
// remove 'drawer_container' key
$base_element_parents = array_slice($element_parents, 0, -1);
// Call drawer_plugin submitConfigurationForm(),
// submitting should be done before $form_state->unsetValue() after restructuring the form_state values, see below.
// @todo: it is not correct to call submit inside a validate method (validateDrawerContainerSubForm())
// also see https://www.drupal.org/node/2820359 for discussion on a #element_submit property
//$full_form = $form_state->getCompleteForm();
$subform = $form['drawer_config'];
$sub_form_state = SubformState::createForSubform($subform, $full_form, $form_state);
$visualn_style_id = $form_state->getValue(array_merge($base_element_parents, ['visualn_style_id']));
$visualn_style = \Drupal::service('entity_type.manager')->getStorage('visualn_style')->load($visualn_style_id);
$drawer_plugin = $visualn_style->getDrawerPlugin();
$drawer_plugin->submitConfigurationForm($subform, $sub_form_state);
// move drawer_config two levels up (remove 'drawer_container' and $drawer_container_key) in form_state values
$drawer_config_values = $form_state->getValue(array_merge($element_parents, [$drawer_container_key, 'drawer_config']));
if (!is_null($drawer_config_values)) {
$form_state->setValue(array_merge($base_element_parents, ['drawer_config']), $drawer_config_values);
}
// move drawer_fields two levels up (remove 'drawer_container' and $drawer_container_key) in form_state values
$drawer_fields_values = $form_state->getValue(array_merge($element_parents, [$drawer_container_key, 'drawer_fields']));
if (!is_null($drawer_fields_values)) {
$new_drawer_fields_values = [];
foreach ($drawer_fields_values as $drawer_field_key => $drawer_field) {
$new_drawer_fields_values[$drawer_field_key] = $drawer_field['field'];
}
$form_state->setValue(array_merge($base_element_parents, ['drawer_fields']), $new_drawer_fields_values);
}
// remove remove 'drawer_container' key itself from form_state
$form_state->unsetValue(array_merge($element_parents, [$drawer_container_key]));
}
public static function doProcessBaseDrawerSubform(array $element, FormStateInterface $form_state, $form, $configuration) {
// @todo: how to check if the form is fresh
// is null basically means that the form is fresh (maybe check the whole $form_state->getValues() to be sure?)
$drawer_element_parents = array_slice($element['#parents'], 0, -1);
// since the function if called as a #process callback and the drawer_plugin_id select was already processed
// and the values were mapped then it is enough to get form_state value for it and no need to check
// configuration value (see FormBuilder::processForm() and FormBuilder::doBuildForm())
// and no need in "is_null($visualn_style_id) then set value from config"
$drawer_plugin_id = $form_state->getValue(array_merge($drawer_element_parents, ['drawer_plugin_id']));
// If it is a fresh form (is_null($visualn_style_id)) or an empty option selected ($visualn_style_id == ""),
// there is nothing to attach for drawer config.
if (!$drawer_plugin_id) {
return $element;
}
$visualNDrawerManager = \Drupal::service('plugin.manager.visualn.drawer');
// Intentionally instantiate a plugin with default configuration.
$drawer_plugin = $visualNDrawerManager->createInstance($drawer_plugin_id, []);
if ($drawer_plugin_id == $configuration['drawer_plugin_id']) {
$drawer_config = $configuration['drawer_config'];
$drawer_plugin->setConfiguration($drawer_config);
}
else {
$drawer_config = $drawer_plugin->getConfiguration();
}
$drawer_container_key = $drawer_plugin_id;
// get drawer configuration form
$element[$drawer_container_key]['drawer_config'] = [];
$element[$drawer_container_key]['drawer_config'] += [
'#parents' => array_merge($element['#parents'], [$drawer_container_key, 'drawer_config']),
'#array_parents' => array_merge($element['#array_parents'], [$drawer_container_key, 'drawer_config']),
];
$subform_state = SubformState::createForSubform($element[$drawer_container_key]['drawer_config'], $form, $form_state);
// attach drawer configuration form
$element[$drawer_container_key]['drawer_config']
= $drawer_plugin->buildConfigurationForm($element[$drawer_container_key]['drawer_config'], $subform_state);
// since drawer and fields onfiguration forms may be empty, do a check (then it shouldn't be of details type)
if (Element::children($element[$drawer_container_key]['drawer_config'])) {
$drawer_element_array_parents = array_slice($element['#array_parents'], 0, -1);
// check that the triggering element is visualn_style_id but not fetcher_id select (or some other element) itself
if ($form_state->getTriggeringElement()) {
$triggering_element = $form_state->getTriggeringElement();
$details_open = $triggering_element['#array_parents'] === array_merge($drawer_element_array_parents, ['drawer_plugin_id']);
$element[$drawer_container_key] = [
'#type' => 'details',
'#title' => t('Base Drawer configuration'),
'#open' => $details_open,
] + $element[$drawer_container_key];
}
}
$element[$drawer_container_key]['#element_validate'] = [[get_called_class(), 'validateBaseDrawerSubForm']];
// @todo: uncomment when #element_submit is introduced into core
//$element[$drawer_container_key]['#element_submit'] = [[get_called_class(), 'submitBaseDrawerSubForm']];
return $element;
}
// @todo: this is based on ResourceGenericDrawerFetcher::processDrawerContainerSubform()
public static function validateBaseDrawerSubForm(&$form, FormStateInterface $form_state) {
// @todo: the code here should actually go to #element_submit, but it is not implemented at the moment in Drupal core
$visualNDrawerManager = \Drupal::service('plugin.manager.visualn.drawer');
// Here the full form_state (e.g. not SubformStateInterface) is supposed to be
// since validation is done after the whole form is rendered.
// get drawer_container_key (for selected visualn style is equal by convention to visualn_style_id,
// see processDrawerContainerSubform() #process callback)
$element_parents = $form['#parents'];
// use $drawer_container_key for clarity though may get rid of array_pop() here and use end($element_parents)
$drawer_container_key = array_pop($element_parents);
// remove 'drawer_container' key
$base_element_parents = array_slice($element_parents, 0, -1);
// Call drawer_plugin submitConfigurationForm(),
// submitting should be done before $form_state->unsetValue() after restructuring the form_state values, see below.
// @todo: it is not correct to call submit inside a validate method (validateDrawerContainerSubForm())
// also see https://www.drupal.org/node/2820359 for discussion on a #element_submit property
// @todo: get full_form from the method arguments
$full_form = $form_state->getCompleteForm();
$subform = $form['drawer_config'];
$sub_form_state = SubformState::createForSubform($subform, $full_form, $form_state);
$drawer_plugin_id = $form_state->getValue(array_merge($base_element_parents, ['drawer_plugin_id']));
// @todo: no need in drawer_config here since submitConfigurationForm() should fully rely on form_state values
$drawer_plugin = $visualNDrawerManager->createInstance($drawer_plugin_id, []);
$drawer_plugin->submitConfigurationForm($subform, $sub_form_state);
// move drawer_config two levels up (remove 'drawer_container' and $drawer_container_key) in form_state values
$drawer_config_values = $form_state->getValue(array_merge($element_parents, [$drawer_container_key, 'drawer_config']));
if (!is_null($drawer_config_values)) {
$form_state->setValue(array_merge($base_element_parents, ['drawer_config']), $drawer_config_values);
}
// remove remove 'drawer_container' key itself from form_state
$form_state->unsetValue(array_merge($element_parents, [$drawer_container_key]));
}
public static function doProcessProviderContainerSubform(array $element, FormStateInterface $form_state, $form) {
$stored_configuration = $element['#stored_configuration'];
$configuration = [
'resource_provider_id' => $stored_configuration['resource_provider_id'],
'resource_provider_config' => $stored_configuration['resource_provider_config'],
];
$context_entity_type = $element['#entity_type'] ?: '';
$context_bundle = $element['#bundle'] ?: '';
$provider_element_parents = array_slice($element['#parents'], 0, -1);
$resource_provider_id = $form_state->getValue(array_merge($provider_element_parents, ['resource_provider_id']));
// If it is a fresh form (is_null($resource_provider_id)) or an empty option selected ($resource_provider_id == ""),
// there is nothing to attach for provider config.
if (!$resource_provider_id) {
return $element;
}
if ($resource_provider_id == $configuration['resource_provider_id']) {
$resource_provider_config = $configuration['resource_provider_config'];
}
else {
$resource_provider_config = [];
}
$visualNResourceProviderManager = \Drupal::service('plugin.manager.visualn.resource_provider');
$provider_plugin = $visualNResourceProviderManager->createInstance($resource_provider_id, $resource_provider_config);
// @todo: maybe just pass all available contexts
// Set "entity_type" and "bundle" contexts
$context_entity_type = new Context(new ContextDefinition('string', NULL, TRUE), $context_entity_type);
$provider_plugin->setContext('entity_type', $context_entity_type);
$context_bundle = new Context(new ContextDefinition('string', NULL, TRUE), $context_bundle);
$provider_plugin->setContext('bundle', $context_bundle);
// @todo: see the note regarding setting context in VisualNResourceProviderItem class
$provider_container_key = $resource_provider_id;
// get provider configuration form
$element[$provider_container_key]['provider_config'] = [];
$element[$provider_container_key]['provider_config'] += [
'#parents' => array_merge($element['#parents'], [$provider_container_key, 'provider_config']),
'#array_parents' => array_merge($element['#array_parents'], [$provider_container_key, 'provider_config']),
];
$subform_state = SubformState::createForSubform($element[$provider_container_key]['provider_config'], $form, $form_state);
// attach provider configuration form
$element[$provider_container_key]['provider_config']
= $provider_plugin->buildConfigurationForm($element[$provider_container_key]['provider_config'], $subform_state);
// since provider configuration form may be empty, do a check (then it shouldn't be of details type)
if (Element::children($element[$provider_container_key]['provider_config'])) {
$provider_element_array_parents = array_slice($element['#array_parents'], 0, -1);
// check that the triggering element is resource_provider_id but not fetcher_id select (or some other element) itself
$details_open = FALSE;
if ($form_state->getTriggeringElement()) {
$triggering_element = $form_state->getTriggeringElement();
$details_open = $triggering_element['#array_parents'] === array_merge($provider_element_array_parents, ['resource_provider_id']);
}
// @todo: take it out everywhere else
$element[$provider_container_key] = [
'#type' => 'details',
'#title' => t('Provider configuration'),
'#open' => $details_open,
] + $element[$provider_container_key];
}
// @todo: replace with #element_submit when introduced into core
// extract values for provider_container subform and provider_config
// remove provider_container key from form_state values path
// also it can be done in ::submitConfigurationForm()
$element[$provider_container_key]['#element_validate'] = [[get_called_class(), 'validateProviderContainerSubForm']];
//$element[$provider_container_key]['#element_validate'] = [[get_called_class(), 'submitDrawerContainerSubForm']];
return $element;
}
// @todo: Restructuring form_state values (removing provider_container key) should be moved
// into #element_submit callback when introduced.
// This is based on VisualNFormsHelper::validateDrawerContainerSubForm().
public static function validateProviderContainerSubForm(&$form, FormStateInterface $form_state, $full_form) {
// @todo: the code here should actually go to #element_submit, but it is not implemented at the moment in Drupal core
// Here the full form_state (e.g. not SubformStateInterface) is supposed to be
// since validation is done after the whole form is rendered.
// get provider_container_key (for selected provider is equal by convention to resource_provider_id,
// see processProviderContainerSubform() #process callback)
$element_parents = $form['#parents'];
// use $provider_container_key for clarity though may get rid of array_pop() here and use end($element_parents)
$provider_container_key = array_pop($element_parents);
// remove 'provider_container' key
$base_element_parents = array_slice($element_parents, 0, -1);
// Call provider_plugin submitConfigurationForm(),
// submitting should be done before $form_state->unsetValue() after restructuring the form_state values, see below.
// @todo: it is not correct to call submit inside a validate method (validateDrawerContainerSubForm())
// also see https://www.drupal.org/node/2820359 for discussion on a #element_submit property
//$full_form = $form_state->getCompleteForm();
$subform = $form['provider_config'];
$sub_form_state = SubformState::createForSubform($subform, $full_form, $form_state);
$visualNResourceProviderManager = \Drupal::service('plugin.manager.visualn.resource_provider');
$resource_provider_id = $form_state->getValue(array_merge($base_element_parents, ['resource_provider_id']));
// The submit callback shouldn't depend on plugin configuration, it relies only on form_state values.
$resource_provider_config = [];
$provider_plugin = $visualNResourceProviderManager->createInstance($resource_provider_id, $resource_provider_config);
$provider_plugin->submitConfigurationForm($subform, $sub_form_state);
// move provider_config two levels up (remove 'provider_container' and $provider_container_key) in form_state values
$provider_config_values = $form_state->getValue(array_merge($element_parents, [$provider_container_key, 'provider_config']));
if (!is_null($provider_config_values)) {
$form_state->setValue(array_merge($base_element_parents, ['resource_provider_config']), $provider_config_values);
}
// remove remove 'provider_container' key itself from form_state
$form_state->unsetValue(array_merge($element_parents, [$provider_container_key]));
// also unset 'provider_container' key if empty
// this check is added in case something else is added to the container by extending classes
// @todo: actually the same check should be added before unsetting provider_container_key (and
// to other places where the same logic with config forms is implemented)
if (!$form_state->getValue($element_parents)) {
$form_state->unsetValue($element_parents);
}
}
public static function doProcessGeneratorContainerSubform(array $element, FormStateInterface $form_state, $form, $configuration) {
$generator_element_parents = array_slice($element['#parents'], 0, -1);
$data_generator_id = $form_state->getValue(array_merge($generator_element_parents, ['data_generator_id']));
// If it is a fresh form (is_null($data_generator_id)) or an empty option selected ($data_generator_id == ""),
// there is nothing to attach for generator config.
if (!$data_generator_id) {
return $element;
}
if ($data_generator_id == $configuration['data_generator_id']) {
$data_generator_config = $configuration['data_generator_config'];
}
else {
$data_generator_config = [];
}
$visualNDataGeneratorManager = \Drupal::service('plugin.manager.visualn.data_generator');
$generator_plugin = $visualNDataGeneratorManager->createInstance($data_generator_id, $data_generator_config);
$generator_container_key = $data_generator_id;
// get generator configuration form
$element[$generator_container_key]['generator_config'] = [];
$element[$generator_container_key]['generator_config'] += [
'#parents' => array_merge($element['#parents'], [$generator_container_key, 'generator_config']),
'#array_parents' => array_merge($element['#array_parents'], [$generator_container_key, 'generator_config']),
];
$subform_state = SubformState::createForSubform($element[$generator_container_key]['generator_config'], $form, $form_state);
// attach generator configuration form
$element[$generator_container_key]['generator_config']
= $generator_plugin->buildConfigurationForm($element[$generator_container_key]['generator_config'], $subform_state);
// since generator configuration form may be empty, do a check (then it shouldn't be of details type)
if (Element::children($element[$generator_container_key]['generator_config'])) {
$generator_element_array_parents = array_slice($element['#array_parents'], 0, -1);
// check that the triggering element is data_generator_id but not fetcher_id or resource_provider_id select (or some other element) itself
$details_open = FALSE;
if ($form_state->getTriggeringElement()) {
$triggering_element = $form_state->getTriggeringElement();
$details_open = $triggering_element['#array_parents'] === array_merge($generator_element_array_parents, ['data_generator_id']);
}
// @todo: take it out everywhere else
$element[$generator_container_key] = [
'#type' => 'details',
'#title' => t('Generator configuration'),
'#open' => $details_open,
] + $element[$generator_container_key];
}
// @todo: replace with #element_submit when introduced into core
// extract values for generator_container subform and generator_config
// remove generator_container key from form_state values path
// also it can be done in ::submitConfigurationForm()
$element[$generator_container_key]['#element_validate'] = [[get_called_class(), 'validateGeneratorContainerSubForm']];
//$element[$generator_container_key]['#element_validate'] = [[get_called_class(), 'submitDrawerContainerSubForm']];
return $element;
}
// @todo: Restructuring form_state values (removing generator_container key) should be moved
// into #element_submit callback when introduced.
// This is based on VisualNFormsHelper::validateDrawerContainerSubForm().
public static function validateGeneratorContainerSubForm(&$form, FormStateInterface $form_state, $full_form) {
// @todo: the code here should actually go to #element_submit, but it is not implemented at the moment in Drupal core
// Here the full form_state (e.g. not SubformStateInterface) is supposed to be
// since validation is done after the whole form is rendered.
// get generator_container_key (for selected generator is equal by convention to data_generator_id,
// see processGeneratorContainerSubform() #process callback)
$element_parents = $form['#parents'];
// use $generator_container_key for clarity though may get rid of array_pop() here and use end($element_parents)
$generator_container_key = array_pop($element_parents);
// remove 'generator_container' key
$base_element_parents = array_slice($element_parents, 0, -1);
// Call generator_plugin submitConfigurationForm(),
// submitting should be done before $form_state->unsetValue() after restructuring the form_state values, see below.
// @todo: it is not correct to call submit inside a validate method (validateDrawerContainerSubForm())
// also see https://www.drupal.org/node/2820359 for discussion on a #element_submit property
//$full_form = $form_state->getCompleteForm();
$subform = $form['generator_config'];
$sub_form_state = SubformState::createForSubform($subform, $full_form, $form_state);
$visualNDataGeneratorManager = \Drupal::service('plugin.manager.visualn.data_generator');
$data_generator_id = $form_state->getValue(array_merge($base_element_parents, ['data_generator_id']));
// The submit callback shouldn't depend on plugin configuration, it relies only on form_state values.
$data_generator_config = [];
$generator_plugin = $visualNDataGeneratorManager->createInstance($data_generator_id, $data_generator_config);
$generator_plugin->submitConfigurationForm($subform, $sub_form_state);
// move generator_config two levels up (remove 'generator_container' and $generator_container_key) in form_state values
$generator_config_values = $form_state->getValue(array_merge($element_parents, [$generator_container_key, 'generator_config']));
if (!is_null($generator_config_values)) {
$form_state->setValue(array_merge($base_element_parents, ['data_generator_config']), $generator_config_values);
}
// remove remove 'generator_container' key itself from form_state
$form_state->unsetValue(array_merge($element_parents, [$generator_container_key]));
// also unset 'generator_container' key if empty
// this check is added in case something else is added to the container by extending classes
// @todo: actually the same check should be added before unsetting generator_container_key (and
// to other places where the same logic with config forms is implemented)
if (!$form_state->getValue($element_parents)) {
$form_state->unsetValue($element_parents);
}
}
/**
* Process #ajax elements. If drawer configuration form uses #ajax to rebuild elements on cerain events,
* those calls must use views specific 'url' setting or new elements values won't be saved.
*
* @todo: based on views display style VisualNDrawing::replaceAjaxOptions()
*/
protected static function replaceAjaxOptions(&$element, FormStateInterface $form_state, $ajax_wrapper_id, $base_depth = NULL) {
if (is_null($base_depth)) {
// get base_depth without 'drawer_container' key
$base_depth = count($element['#array_parents']) - 1;
}
foreach (Element::children($element) as $key) {
if (isset($element[$key]['#ajax'])) {
$element[$key]['#ajax'] = [
'callback' => [get_called_class(), 'ajaxCallback2'],
'wrapper' => $ajax_wrapper_id,
// we can't use element_depth since in some cases it will contain wrong value,
// e.g. when using #ajax with 'radios', each radio button inherits parent element
// element_depth value (with all other #ajax settings) though the real value is greater by one
// @todo: maybe set as $element[$key]['#drawer_config_base_element_depth']
'base_depth' => $base_depth,
];
}
// check subtree elements
static::replaceAjaxOptions($element[$key], $form_state, $ajax_wrapper_id, $base_depth);
}
}
public static function ajaxCallback2(array $form, FormStateInterface $form_state, Request $request) {
$triggering_element = $form_state->getTriggeringElement();
// the base_depth value is set in replaceAjaxOptions()
$base_depth = $triggering_element['#ajax']['base_depth'];
// get parent element of the whole config form (parent element for drawer_container), including data keys subform
$triggering_element_parents = array_slice($triggering_element['#array_parents'], 0, $base_depth);
$element = NestedArray::getValue($form, $triggering_element_parents);
return $element['drawer_container'];
}
// @todo: mostly based on VisualNFormsHelper::doProcessGeneratorContainerSubform()
public static function doProcessSkinContainerSubform(array $element, FormStateInterface $form_state, $form, $configuration) {
$skin_element_parents = array_slice($element['#parents'], 0, -1);
$drawer_skin_id = $form_state->getValue(array_merge($skin_element_parents, ['drawer_skin_id']));
// If it is a fresh form (is_null($drawer_skin_id)) or an empty option selected ($drawer_skin_id == ""),
// there is nothing to attach for skin config.
if (!$drawer_skin_id) {
return $element;
}
if ($drawer_skin_id == $configuration['drawer_skin_id']) {
$drawer_skin_config = $configuration['drawer_skin_config'];
}
else {
$drawer_skin_config = [];
}
$visualNDrawerSkinManager = \Drupal::service('plugin.manager.visualn.drawer_skin');
$skin_plugin = $visualNDrawerSkinManager->createInstance($drawer_skin_id, $drawer_skin_config);
$skin_container_key = $drawer_skin_id;
// get skin configuration form
$element[$skin_container_key]['skin_config'] = [];
$element[$skin_container_key]['skin_config'] += [
'#parents' => array_merge($element['#parents'], [$skin_container_key, 'skin_config']),
'#array_parents' => array_merge($element['#array_parents'], [$skin_container_key, 'skin_config']),
];
$subform_state = SubformState::createForSubform($element[$skin_container_key]['skin_config'], $form, $form_state);
// attach skin configuration form
$element[$skin_container_key]['skin_config']
= $skin_plugin->buildConfigurationForm($element[$skin_container_key]['skin_config'], $subform_state);
// since skin configuration form may be empty, do a check (then it shouldn't be of details type)
if (Element::children($element[$skin_container_key]['skin_config'])) {
$skin_element_array_parents = array_slice($element['#array_parents'], 0, -1);
// check that the triggering element is drawer_skin_id but not fetcher_id or resource_provider_id select (or some other element) itself
$details_open = FALSE;
if ($form_state->getTriggeringElement()) {
$triggering_element = $form_state->getTriggeringElement();
$details_open = $triggering_element['#array_parents'] === array_merge($skin_element_array_parents, ['drawer_skin_id']);
}
// @todo: take it out everywhere else
$element[$skin_container_key] = [
'#type' => 'details',
'#title' => t('Skin configuration'),
'#open' => $details_open,
] + $element[$skin_container_key];
}
// @todo: replace with #element_submit when introduced into core
// extract values for skin_container subform and skin_config
// remove skin_container key from form_state values path
// also it can be done in ::submitConfigurationForm()
$element[$skin_container_key]['#element_validate'] = [[get_called_class(), 'validateSkinContainerSubForm']];
//$element[$skin_container_key]['#element_validate'] = [[get_called_class(), 'submitDrawerContainerSubForm']];
return $element;
}
// @todo: Restructuring form_state values (removing skin_container key) should be moved
// into #element_submit callback when introduced.
// This is based on VisualNFormsHelper::validateDrawerContainerSubForm().
public static function validateSkinContainerSubForm(&$form, FormStateInterface $form_state, $full_form) {
// @todo: the code here should actually go to #element_submit, but it is not implemented at the moment in Drupal core
// Here the full form_state (e.g. not SubformStateInterface) is supposed to be
// since validation is done after the whole form is rendered.
// get skin_container_key (for selected skin is equal by convention to drawer_skin_id,
// see processGeneratorContainerSubform() #process callback)
$element_parents = $form['#parents'];
// use $skin_container_key for clarity though may get rid of array_pop() here and use end($element_parents)
$skin_container_key = array_pop($element_parents);
// remove 'skin_container' key
$base_element_parents = array_slice($element_parents, 0, -1);
// Call skin_plugin submitConfigurationForm(),
// submitting should be done before $form_state->unsetValue() after restructuring the form_state values, see below.
// @todo: it is not correct to call submit inside a validate method (validateDrawerContainerSubForm())
// also see https://www.drupal.org/node/2820359 for discussion on a #element_submit property
//$full_form = $form_state->getCompleteForm();
$subform = $form['skin_config'];
$sub_form_state = SubformState::createForSubform($subform, $full_form, $form_state);
$visualNDrawerSkinManager = \Drupal::service('plugin.manager.visualn.drawer_skin');
$drawer_skin_id = $form_state->getValue(array_merge($base_element_parents, ['drawer_skin_id']));
// The submit callback shouldn't depend on plugin configuration, it relies only on form_state values.
$drawer_skin_config = [];
$skin_plugin = $visualNDrawerSkinManager->createInstance($drawer_skin_id, $drawer_skin_config);
$skin_plugin->submitConfigurationForm($subform, $sub_form_state);
// move skin_config two levels up (remove 'skin_container' and $skin_container_key) in form_state values
$skin_config_values = $form_state->getValue(array_merge($element_parents, [$skin_container_key, 'skin_config']));
if (!is_null($skin_config_values)) {
$form_state->setValue(array_merge($base_element_parents, ['drawer_skin_config']), $skin_config_values);
}
// remove remove 'skin_container' key itself from form_state
$form_state->unsetValue(array_merge($element_parents, [$skin_container_key]));
// also unset 'skin_container' key if empty
// this check is added in case something else is added to the container by extending classes
// @todo: actually the same check should be added before unsetting skin_container_key (and
// to other places where the same logic with config forms is implemented)
if (!$form_state->getValue($element_parents)) {
$form_state->unsetValue($element_parents);
}
}
// @todo: convert into a generic doProcessPluginContainerSubform #process callback
// since it is used for multiple plugins config subforms almost unchanged
// @todo: mostly based on VisualNFormsHelper::doProcessGeneratorContainerSubform()
public static function doProcessSetupBakerContainerSubform(array $element, FormStateInterface $form_state, $form, $configuration) {
$baker_element_parents = array_slice($element['#parents'], 0, -1);
$setup_baker_id = $form_state->getValue(array_merge($baker_element_parents, ['setup_baker_id']));
// If it is a fresh form (is_null($setup_baker_id)) or an empty option selected ($setup_baker_id == ""),
// there is nothing to attach for baker config.
if (!$setup_baker_id) {
return $element;
}
if ($setup_baker_id == $configuration['setup_baker_id']) {
$setup_baker_config = $configuration['setup_baker_config'];
}
else {
$setup_baker_config = [];
}
$visualNSetupBakerManager = \Drupal::service('plugin.manager.visualn.setup_baker');
$baker_plugin = $visualNSetupBakerManager->createInstance($setup_baker_id, $setup_baker_config);
$baker_container_key = $setup_baker_id;
// get baker configuration form
$element[$baker_container_key]['baker_config'] = [];
$element[$baker_container_key]['baker_config'] += [
'#parents' => array_merge($element['#parents'], [$baker_container_key, 'baker_config']),
'#array_parents' => array_merge($element['#array_parents'], [$baker_container_key, 'baker_config']),
];
$subform_state = SubformState::createForSubform($element[$baker_container_key]['baker_config'], $form, $form_state);
// attach baker configuration form
$element[$baker_container_key]['baker_config']
= $baker_plugin->buildConfigurationForm($element[$baker_container_key]['baker_config'], $subform_state);
// since baker configuration form may be empty, do a check (then it shouldn't be of details type)
if (Element::children($element[$baker_container_key]['baker_config'])) {
$baker_element_array_parents = array_slice($element['#array_parents'], 0, -1);
// check that the triggering element is setup_baker_id but not fetcher_id or resource_provider_id select (or some other element) itself
$details_open = FALSE;
if ($form_state->getTriggeringElement()) {
$triggering_element = $form_state->getTriggeringElement();
$details_open = $triggering_element['#array_parents'] === array_merge($baker_element_array_parents, ['setup_baker_id']);
}
// @todo: take it out everywhere else
$element[$baker_container_key] = [
'#type' => 'details',
'#title' => t('Baker configuration'),
'#open' => $details_open,
] + $element[$baker_container_key];
}
// @todo: replace with #element_submit when introduced into core
// extract values for baker_container subform and baker_config
// remove baker_container key from form_state values path
// also it can be done in ::submitConfigurationForm()
$element[$baker_container_key]['#element_validate'] = [[get_called_class(), 'validateSetupBakerContainerSubForm']];
//$element[$baker_container_key]['#element_validate'] = [[get_called_class(), 'submitDrawerContainerSubForm']];
return $element;
}
// @todo: Restructuring form_state values (removing baker_container key) should be moved
// into #element_submit callback when introduced.
// This is based on VisualNFormsHelper::validateDrawerContainerSubForm().
public static function validateSetupBakerContainerSubForm(&$form, FormStateInterface $form_state, $full_form) {
// @todo: the code here should actually go to #element_submit, but it is not implemented at the moment in Drupal core
// Here the full form_state (e.g. not SubformStateInterface) is supposed to be
// since validation is done after the whole form is rendered.
// get baker_container_key (for selected baker is equal by convention to setup_baker_id,
// see processGeneratorContainerSubform() #process callback)
$element_parents = $form['#parents'];
// use $baker_container_key for clarity though may get rid of array_pop() here and use end($element_parents)
$baker_container_key = array_pop($element_parents);
// remove 'baker_container' key
$base_element_parents = array_slice($element_parents, 0, -1);
// Call baker_plugin submitConfigurationForm(),
// submitting should be done before $form_state->unsetValue() after restructuring the form_state values, see below.
// @todo: it is not correct to call submit inside a validate method (validateDrawerContainerSubForm())
// also see https://www.drupal.org/node/2820359 for discussion on a #element_submit property
//$full_form = $form_state->getCompleteForm();
$subform = $form['baker_config'];
$sub_form_state = SubformState::createForSubform($subform, $full_form, $form_state);
$visualNSetupBakerManager = \Drupal::service('plugin.manager.visualn.setup_baker');
$setup_baker_id = $form_state->getValue(array_merge($base_element_parents, ['setup_baker_id']));
// The submit callback shouldn't depend on plugin configuration, it relies only on form_state values.
$setup_baker_config = [];
$baker_plugin = $visualNSetupBakerManager->createInstance($setup_baker_id, $setup_baker_config);
$baker_plugin->submitConfigurationForm($subform, $sub_form_state);
// move baker_config two levels up (remove 'baker_container' and $baker_container_key) in form_state values
$baker_config_values = $form_state->getValue(array_merge($element_parents, [$baker_container_key, 'baker_config']));
if (!is_null($baker_config_values)) {
$form_state->setValue(array_merge($base_element_parents, ['setup_baker_config']), $baker_config_values);
}
// remove remove 'baker_container' key itself from form_state
$form_state->unsetValue(array_merge($element_parents, [$baker_container_key]));
// also unset 'baker_container' key if empty
// this check is added in case something else is added to the container by extending classes
// @todo: actually the same check should be added before unsetting baker_container_key (and
// to other places where the same logic with config forms is implemented)
if (!$form_state->getValue($element_parents)) {
$form_state->unsetValue($element_parents);
}
}
}
