cms_content_sync-3.0.x-dev/src/Form/FlowForm.php

src/Form/FlowForm.php
<?php

namespace Drupal\cms_content_sync\Form;

use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Entity\Pool;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\cms_content_sync\Plugin\Type\FieldHandlerPluginManager;
use Drupal\cms_content_sync\PullIntent;
use Drupal\cms_content_sync\PushIntent;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Cache\CacheTagsInvalidator;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\EntityFieldManager;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Form handler for the Flow add and edit forms.
 */
class FlowForm extends EntityForm {
  /**
   * @var string cms_content_sync_PREVIEW_FIELD
   *             The name of the view mode that must be present to allow teaser previews
   */
  public const cms_content_sync_PREVIEW_FIELD = 'cms_content_sync_preview';

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $bundleInfoService;

  /**
   * @var \Drupal\Core\Entity\EntityFieldManager
   */
  protected $entityFieldManager;

  /**
   * @var \Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager
   */
  protected $entityPluginManager;

  /**
   * @var \Drupal\cms_content_sync\Plugin\Type\FieldHandlerPluginManager
   */
  protected $fieldPluginManager;

  /**
   * The Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * The config factory to load configuration.
   *
   * @var \Drupal\Core\Config\ConfigFactory
   */
  protected $configFactory;

  /**
   * The Drupal Core Cache Tags Invalidator.
   *
   * @var \Drupal\Core\Cache\CacheTagsInvalidator
   */
  protected $cacheTagsInvalidator;

  /**
   * @var string
   */
  protected $triggeringType;

  /**
   * @var string
   */
  protected $triggeringBundle;

  /**
   * @var string
   */
  protected $triggeredAction;

  /**
   * @var string[][]
   */
  protected $ajaxReplaceElements = [];

  /**
   * Constructs an object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity query.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info_service
   *   The bundle info service.
   * @param \Drupal\Core\Entity\EntityFieldManager $entity_field_manager
   *   The entity field manager.
   * @param \Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager $entity_plugin_manager
   *   The content sync entity manager.
   * @param \Drupal\cms_content_sync\Plugin\Type\FieldHandlerPluginManager $field_plugin_manager
   *   The content sync field plugin manager.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   * @param \Drupal\Core\Config\ConfigFactory $config_factory
   *   The messenger service.
   * @param \Drupal\Core\Cache\CacheTagsInvalidator $cache_tags_invalidator
   *   The Drupal Core Cache Tags Invalidator.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    EntityTypeBundleInfoInterface $bundle_info_service,
    EntityFieldManager $entity_field_manager,
    EntityHandlerPluginManager $entity_plugin_manager,
    FieldHandlerPluginManager $field_plugin_manager,
    MessengerInterface $messenger,
    ConfigFactory $config_factory,
    CacheTagsInvalidator $cache_tags_invalidator,
  ) {
    $this->entityTypeManager = $entity_type_manager;
    $this->bundleInfoService = $bundle_info_service;
    $this->entityFieldManager = $entity_field_manager;
    $this->entityPluginManager = $entity_plugin_manager;
    $this->fieldPluginManager = $field_plugin_manager;
    $this->messenger = $messenger;
    $this->configFactory = $config_factory;
    $this->cacheTagsInvalidator = $cache_tags_invalidator;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
    $container->get('entity_type.manager'),
    $container->get('entity_type.bundle.info'),
    $container->get('entity_field.manager'),
    $container->get('plugin.manager.cms_content_sync_entity_handler'),
    $container->get('plugin.manager.cms_content_sync_field_handler'),
    $container->get('messenger'),
    $container->get('config.factory'),
    $container->get('cache_tags.invalidator')
    );
  }

  /**
   * A sync handler has been updated, so the options must be updated as well.
   * We're simply reloading the table in this case.
   *
   * @param array $form
   *
   * @return array
   *   The new per_bundle_settings table
   */
  public function updateSyncHandler($form, FormStateInterface $form_state) {
    return $form['per_bundle_settings'];
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $type = $this->getCurrentFormType();

    // Add form library for some custom styling.
    $form['#attached']['library'][] = 'cms_content_sync/flow-form';

    if (!$type) {
      return $this->selectTypeForm($form, $form_state);
    }

    // Before a flow can be created, at least one pool must exist.
    // Get all pool entities.
    $pool_entities = Pool::getAll();

    if (empty($pool_entities)) {
      global $base_url;
      $path = Url::fromRoute('cms_content_sync.cms_content_sync_pool.pool_required')
        ->toString();
      $response = new RedirectResponse($base_url . $path);
      $response->send();
    }

    $form = parent::form($form, $form_state);
    $form['#tree'] = TRUE;

    if ($this->shouldOpenAll($form_state)) {
      $form['open_all'] = [
        '#type' => 'hidden',
        '#value' => '1',
      ];
    }

    /**
     * @var \Drupal\cms_content_sync\Entity\Flow $flow
     */
    $flow = $this->entity;

    $form['name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Name'),
      '#maxlength' => 255,
      '#attributes' => [
        'autofocus' => 'autofocus',
      ],
      '#default_value' => $flow->label(),
      '#description' => $this->t('An administrative name describing the workflow intended to be achieved with this synchronization.'),
      '#required' => TRUE,
    ];

    $form['id'] = [
      '#type' => 'machine_name',
      '#default_value' => $flow->id(),
      '#machine_name' => [
        'exists' => [$this, 'exists'],
        'source' => ['name'],
      ],
      '#disabled' => !$flow->isNew(),
    ];

    $config_machine_name = $flow->id();
    if (!isset($config_machine_name)) {
      $config_machine_name = '<machine_name_of_the_configuration>';
    }

    $flow_id = $flow->id();
    if (isset($flow_id)) {
      $flow_id = 'cms_content_sync.flow.' . $flow_id;
      $non_overridden_config = $this->configFactory->get($flow_id)
        ->getRawData();
      $non_overridden_flow_status = $non_overridden_config['status'] ?? NULL;
    }

    $flow_status_description = '';
    $active_flow_status = $this->configFactory->get($flow_id)->get('status');
    if (isset($non_overridden_flow_status, $active_flow_status)) {
      if ($active_flow_status != $non_overridden_flow_status) {
        $flow_status_description = '<br><b>This value is overridden within the settings.php file.</b>';
      }
    }

    $form['status'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Active'),
      '#default_value' => $non_overridden_flow_status ?? TRUE,
      '#description' => $this->t(
              'If the flow is not active, none of the below configured behaviors will take effect. This configuration could be overwritten within your environment specific settings.php file:<br> <i>@status_config</i>.' . $flow_status_description . '',
              [
                '@status_config' => '$config["cms_content_sync.flow.' . $config_machine_name . '"]["status"] = FALSE;',
              ]
      ),
    ];

    $form['type'] = [
      '#title' => $this->t('Type'),
      '#markup' => '<br>' . $this->t('Right now the this Flow is set to @type. <strong>If you want to change it, first save all changes you made as otherwise they will be lost!</strong> Then click here: ', [
        '@type' => (Flow::TYPE_BOTH === $type ? $this->t('Push and Pull') : (Flow::TYPE_PULL === $type ? $this->t('Pull') : $this->t('Push'))),
      ]) .
      '<a href="?type=">' . $this->t('Change') . '</a><br><br>',
    ];

    $entity_types = $this->bundleInfoService->getAllBundleInfo();
    ksort($entity_types);

    // Remove the Content Sync Entity Status entity type form the array.
    unset($entity_types['cms_content_sync_entity_status']);

    $entity_type_list = [
      '#type' => 'vertical_tabs',
      '#default_tab' => 'edit-node',
      '#tree' => TRUE,
    ];

    $form['entity_type_list'] = $entity_type_list;

    foreach ($entity_types as $entity_type_name => $entity_type) {
      $this->renderEntityType($form, $form_state, $entity_type_name);
    }

    $this->disableOverridenConfigs($form);

    return $form;
  }

  /**
   * Enable bundle / Disable bundle / Show fields => return individual form
   * element to replace.
   *
   * @param array $form
   *
   * @return array the bundle settings
   */
  public function ajaxReturn($form, FormStateInterface $form_state) {
    $entity_type_name = $this->triggeringType;
    $bundle_name = $this->triggeringBundle;

    if ('properties' === $this->triggeredAction) {
      return $form[$entity_type_name][$bundle_name]['properties'];
    }

    return $form[$entity_type_name][$bundle_name];
  }

  /**
   * Enable bundle => show settings..
   *
   * @param array $form
   */
  public function enableBundle($form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $entity_type_name = $trigger['#entity_type'];
    $bundle_name = $trigger['#bundle'];

    $this->triggeringType = $entity_type_name;
    $this->triggeringBundle = $bundle_name;
    $this->triggeredAction = 'enable';

    $this->fixMissingFormStateFromAjax($form, $form_state);

    $form_state->setValue([$entity_type_name, $bundle_name, 'edit'], '1');

    $form_state->setRebuild();
  }

  /**
   * Disable bundle => hide settings.
   *
   * @param array $form
   */
  public function disableBundle($form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $entity_type_name = $trigger['#entity_type'];
    $bundle_name = $trigger['#bundle'];

    $this->triggeringType = $entity_type_name;
    $this->triggeringBundle = $bundle_name;
    $this->triggeredAction = 'disable';

    $this->fixMissingFormStateFromAjax($form, $form_state);

    $form_state->setValue([$entity_type_name, $bundle_name, 'edit'], '0');
    $form_state->setValue([
      $entity_type_name,
      $bundle_name,
      'handler',
    ], 'ignore');

    $form_state->setRebuild();
  }

  /**
   * Show all field settings not just the summary.
   *
   * @param array $form
   */
  public function showAllFields($form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $entity_type_name = $trigger['#entity_type'];
    $bundle_name = $trigger['#bundle'];

    $this->triggeringType = $entity_type_name;
    $this->triggeringBundle = $bundle_name;
    $this->triggeredAction = 'properties';

    $this->fixMissingFormStateFromAjax($form, $form_state);

    $form_state->setValue([
      $entity_type_name,
      $bundle_name,
      'properties',
      'advanced',
      'show-all',
    ], '1');

    $form_state->setRebuild();
  }

  /**
   * Show version mismatches. We need to re-set the missing form state here so
   * the form doesn't break when using this button.
   *
   * @param array $form
   */
  public function showVersionMismatches($form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $entity_type_name = $trigger['#entity_type'];
    $bundle_name = $trigger['#bundle'];

    $this->triggeringType = $entity_type_name;
    $this->triggeringBundle = $bundle_name;
    $this->triggeredAction = 'enable';

    $this->fixMissingFormStateFromAjax($form, $form_state);

    $form_state->setValue([$entity_type_name, $bundle_name, 'edit'], '1');
    $form_state->setValue([
      $entity_type_name,
      $bundle_name,
      'show-version-mismatches',
    ], '1');

    $form_state->setRebuild();
  }

  /**
   * Show all field settings not just the summary.
   *
   * @param array $form
   */
  public function enableAllReferenced($form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $entity_type_name = $trigger['#entity_type'];
    $bundle_name = $trigger['#bundle'];

    $this->triggeringType = $entity_type_name;
    $this->triggeringBundle = $bundle_name;
    $this->triggeredAction = 'properties';

    $this->fixMissingFormStateFromAjax($form, $form_state);

    $referenced_type = $trigger['#referenced_type'];
    $referenced_bundles = $trigger['#referenced_bundles'];

    if ('all' === $referenced_bundles) {
      $entity_types = $this->bundleInfoService->getAllBundleInfo();

      foreach ($entity_types[$referenced_type] as $bundle => $set) {
        if (empty($current_values['per_bundle_settings'][$referenced_type][$bundle]['settings']['handler']) || Flow::HANDLER_IGNORE == $current_values['per_bundle_settings'][$referenced_type][$bundle]['settings']['handler']) {
          $form_state->setValue([$referenced_type, $bundle, 'edit'], '1');

          $this->ajaxReplaceElements[] = [$referenced_type, $bundle];
        }
      }
    }
    else {
      foreach ($referenced_bundles as $bundle) {
        if (empty($current_values['per_bundle_settings'][$referenced_type][$bundle]['settings']['handler']) || Flow::HANDLER_IGNORE == $current_values['per_bundle_settings'][$referenced_type][$bundle]['settings']['handler']) {
          $form_state->setValue([$referenced_type, $bundle, 'edit'], '1');

          $this->ajaxReplaceElements[] = [$referenced_type, $bundle];
        }
      }
    }

    $form_state->setRebuild();
  }

  /**
   * Enable all referenced => return multiple form elements to replace.
   *
   * @param array $form
   *
   * @return \Drupal\Core\Ajax\AjaxResponse AJAX commands to execute
   */
  public function enableAllReferencedReturn($form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    foreach ($this->ajaxReplaceElements as $keys) {
      $entity_type_name = $keys[0];
      $bundle_name = $keys[1];

      $bundle_id = $entity_type_name . '---' . $bundle_name;
      $settings_id = 'per-bundle-settings---' . $bundle_id;

      $response->addCommand(new ReplaceCommand('#' . $settings_id, $form[$entity_type_name][$bundle_name]));
    }

    if ('properties' === $this->triggeredAction) {
      $entity_type_name = $this->triggeringType;
      $bundle_name = $this->triggeringBundle;

      $field_settings_id = 'per-bundle-settings---' . $entity_type_name . '---' . $bundle_name . '---properties';

      $response->addCommand(new ReplaceCommand('#' . $field_settings_id, $form[$entity_type_name][$bundle_name]['properties']));
    }

    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    $current_values = $this->getCurrentValues($form_state);

    $used_pools = [];

    foreach ($current_values['per_bundle_settings'] as $entity_type_name => &$bundles) {
      foreach ($bundles as $bundle_name => &$bundle_settings) {
        $values = $bundle_settings['settings'];
        if (empty($values['handler']) || Flow::HANDLER_IGNORE == $values['handler']) {
          continue;
        }

        $handler = $this->entityPluginManager->createInstance($values['handler'], [
          'entity_type_name' => $entity_type_name,
          'bundle_name' => $bundle_name,
          'settings' => $values,
          'sync' => NULL,
        ]);

        if (!empty($values['export']) && PushIntent::PUSH_DISABLED !== $values['export']) {
          foreach ($values['export_pools'] as $name => $behavior) {
            if (Pool::POOL_USAGE_FORBID !== $behavior) {
              $used_pools[$name] = TRUE;
            }
          }
        }

        if (!empty($values['import']) && PullIntent::PULL_DISABLED !== $values['import']) {
          foreach ($values['import_pools'] as $name => $behavior) {
            if (Pool::POOL_USAGE_FORBID !== $behavior) {
              $used_pools[$name] = TRUE;
            }
          }
        }

        $handler->validateHandlerSettings($form, $form_state, $entity_type_name, $bundle_name, $current_values);

        if (!empty($bundle_settings['properties'])) {
          foreach ($bundle_settings['properties'] as $field => &$field_settings) {
            if (empty($field_settings['handler']) || Flow::HANDLER_IGNORE == $field_settings['handler']) {
              continue;
            }

            /**
             * @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields
             */
            $field_definition = $this->entityFieldManager->getFieldDefinitions($entity_type_name, $bundle_name)[$field];
            $handler = $this->fieldPluginManager->createInstance($field_settings['handler'], [
              'entity_type_name' => $entity_type_name,
              'bundle_name' => $bundle_name,
              'field_name' => $field,
              'field_definition' => $field_definition,
              'settings' => $field_settings,
              'sync' => NULL,
            ]);

            $handler->validateHandlerSettings($form, $form_state, $entity_type_name, $bundle_name, $field, $current_values);
          }
        }
      }
    }

    return parent::validateForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $form, FormStateInterface $form_state) {
    $config = $this->entity;

    $config->{'per_bundle_settings'} = $this->getCurrentValues($form_state)['per_bundle_settings'];

    $per_bundle_settings = &$config->{'per_bundle_settings'};

    $export_menu_items_automatically = FALSE;
    if (isset($per_bundle_settings['menu_link_content']['menu_link_content']) && PushIntent::PUSH_AUTOMATICALLY == $per_bundle_settings['menu_link_content']['menu_link_content']['settings']['export']) {
      $export_menu_items_automatically = TRUE;
    }

    foreach ($per_bundle_settings as $entity_type_name => &$bundles) {
      foreach ($bundles as $bundle_name => &$bundle_config) {
        $settings = &$bundle_config['settings'];
        $settings['version'] = Flow::getEntityTypeVersion($entity_type_name, $bundle_name);

        // If the Flow should pull manually, the Pool selection must also be set to Manual. Otherwise the entities won't
        // show up in the pull dashboard. To protect people from that scenario, we're changing that automatically.
        if (PullIntent::PULL_MANUALLY === $settings['import']) {
          foreach ($settings['import_pools'] as $pool => $setting) {
            if (Pool::POOL_USAGE_FORCE === $setting) {
              $settings['import_pools'][$pool] = Pool::POOL_USAGE_ALLOW;
            }
          }
        }

        // If menu items should be exported automatically, the entity type option "export menu items"
        // have to be disabled to avoid a race condition.
        if (PushIntent::PUSH_DISABLED != $settings['export']) {
          if ($export_menu_items_automatically && isset($settings['handler_settings']['export_menu_items'])) {
            $settings['handler_settings']['export_menu_items'] = 0;
          }
        }
      }
    }

    $config->type = $this->getCurrentFormType();
    $config->variant = Flow::VARIANT_PER_BUNDLE;

    $status = $config->save();

    if ($status) {
      $this->messenger->addMessage($this->t('Saved the %label Flow.', [
        '%label' => $config->label(),
      ]));
    }
    else {
      $this->messenger->addMessage($this->t('The %label Flow could not be saved.', [
        '%label' => $config->label(),
      ]));
    }

    $triggering_element = $form_state->getTriggeringElement();
    if ('submit' == $triggering_element['#parents'][1]) {
      // Make sure that the export is executed.
      \Drupal::request()->query->remove('destination');
      $form_state->setRedirect('entity.cms_content_sync_flow.export', ['cms_content_sync_flow' => $config->id()]);
    }
    else {
      $form_state->setRedirect('entity.cms_content_sync_flow.collection');
    }

    $moduleHandler = \Drupal::service('module_handler');
    if ($moduleHandler->moduleExists('cms_content_sync_developer')) {
      $config_factory = $this->configFactory;
      $developer_config = $config_factory->getEditable('cms_content_sync.developer');
      $mismatching_versions = $developer_config->get('version_mismatch');
      if (!empty($mismatching_versions)) {
        unset($mismatching_versions[$config->id()]);
        $developer_config->set('version_mismatch', $mismatching_versions)
          ->save();
      }
    }

    // Invalidate the admin menu cache to ensure the Content Dashboard Menu gets shown or hidden.
    $this->cacheTagsInvalidator->invalidateTags(['config:system.menu.admin']);
  }

  /**
   * Check if the entity exists.
   *
   * A helper function to check whether an
   * Flow configuration entity exists.
   *
   * @param int $id
   *   An ID of sync.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *
   * @return bool
   *   Checking on exist an entity
   */
  public function exists($id) {
    $entity = $this->entityTypeManager
      ->getStorage('cms_content_sync_flow')
      ->getQuery()
      ->accessCheck(FALSE)
      ->condition('id', $id)
      ->execute();

    return (bool) $entity;
  }

  /**
   *
   */
  protected function getCurrentFormType() {
    if (isset($_GET['type'])) {
      if (in_array($_GET['type'], [
        Flow::TYPE_PULL,
        Flow::TYPE_PUSH,
        Flow::TYPE_BOTH,
      ])) {
        return $_GET['type'];
      }

      return NULL;
    }

    /**
     * @var \Drupal\cms_content_sync\Entity\Flow $flow
     */
    $flow = $this->entity;

    return $flow->type;
  }

  /**
   * If nothing has been configured yet, let the user chose whether to push and
   * pull with this flow. This will make the flow form significantly smaller
   * and simpler to manage.
   *
   * @return array
   */
  protected function selectTypeForm(array $form, FormStateInterface $form_state) {
    $form['type'] = [
      '#title' => $this->t('Type'),
      '#markup' => $this->t('Please select the type of Flow you want to create.') . '<br><br>' .
      $this->t('For content staging select "push" on your stage site and "pull" on your production site.') . '<br><br>' .
      '<ul class="action-links">' .
      '<li><a class="button button-action button--primary button--small flow pull" href="?type=pull">' . $this->t('Pull') . '</a></li>' .
      '<li><a class="button button-action button--primary button--small flow push" href="?type=push">' . $this->t('Push') . '</a></li>' .
      '</ul>',
    ];

    return $form;
  }

  /**
   * Check whether all of the sub-forms per entity type should be open by
   * default. This is important if a Flow has been created without setting all
   * options where the user now edits the Flow once to provide all options
   * provided by the form.
   *
   * @return bool
   */
  protected function shouldOpenAll(FormStateInterface $form_state) {
    return isset($_GET['open']) || !empty($form_state->getValue('open_all'));
  }

  /**
   * Render all bundles for the given entity type. Displayed in vertical tabs
   * from the parent form.
   *
   * @param $entity_type_name
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function renderEntityType(array &$entity_type_list, FormStateInterface $form_state, $entity_type_name) {
    $entity_types = $this->bundleInfoService->getAllBundleInfo();

    $entity_type = $entity_types[$entity_type_name];

    ksort($entity_type);

    $current_values = $this->getCurrentValues($form_state);

    $bundles_rendered = [];

    $open_all = $this->shouldOpenAll($form_state);

    foreach ($entity_type as $bundle_name => $entity_bundle) {
      $info = EntityHandlerPluginManager::getEntityTypeInfo($entity_type_name, $bundle_name);
      if (!empty($info['no_entity_type_handler']) || !empty($info['required_field_not_supported'])) {
        continue;
      }

      $edit = $form_state->getValue([$entity_type_name, $bundle_name, 'edit']);
      if ('properties' === $this->triggeredAction && $this->triggeringType === $entity_type_name && $this->triggeringBundle === $bundle_name) {
        $edit = '1';
      }

      if ($open_all && isset($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler']) && 'ignore' !== $current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler']) {
        $edit = '1';
      }

      // If the version changed, we need to expand this so the new field is added.
      if (isset($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['version']) && 'ignore' !== $current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler']) {
        $version = Flow::getEntityTypeVersion($entity_type_name, $bundle_name);
        if ($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['version'] !== $version) {
          $edit = '1';
        }
      }

      if ('1' == $edit) {
        $bundle_info = $this->renderEnabledBundle($form_state, $entity_type_name, $bundle_name);
      }
      elseif (!isset($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler']) || 'ignore' === $current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler']) {
        $bundle_info = $this->renderDisabledBundle($form_state, $entity_type_name, $bundle_name);
      }
      else {
        $bundle_info = $this->renderBundleSummary($form_state, $entity_type_name, $bundle_name);
      }

      $bundles_rendered[$bundle_name] = $bundle_info;
    }

    if (empty($bundles_rendered)) {
      return;
    }

    $bundles_rendered = array_merge($bundles_rendered, [
      '#type' => 'details',
      '#title' => str_replace('_', ' ', ucfirst($entity_type_name)),
      '#group' => 'entity_type_list',
    ]);

    // Add information text for paragraphs that a specific commit is required.
    if ('paragraph' == $entity_type_name) {
      $bundles_rendered['#description'] = 'If you want to select the pool per paragraph (Push to Pools set to "Allow"), Paragraphs version >= <strong>8.x-1.3</strong> is required.<br><br>';
    }

    $entity_type_list[$entity_type_name] = $bundles_rendered;
  }

  /**
   * Get the current values for the config entity. Data is collected in the
   * following order:
   * - Data from existing config entity
   * - Extended and overwritten by user submitted data (if POST'd already)
   * - Extended by default values for implicit values (e.g. field config not
   * shown, so implicitly fields are included).
   */
  protected function getCurrentValues(FormStateInterface $form_state) {
    /**
     * @var \Drupal\cms_content_sync\Entity\Flow $flow
     */
    $flow = $this->entity;

    $result = [
      'name' => $flow->name,
      'id' => $flow->id,
      'status' => $flow->status(),
      'per_bundle_settings' => $flow->per_bundle_settings,
    ];

    $submitted = $form_state->cleanValues()->getValues();

    $entity_types = $this->bundleInfoService->getAllBundleInfo();

    if (!empty($submitted)) {
      if (isset($submitted['name'])) {
        $result['name'] = $submitted['name'];
      }
      if (isset($submitted['id'])) {
        $result['id'] = $submitted['id'];
      }
      if (isset($submitted['status'])) {
        $result['status'] = $submitted['status'];
      }

      foreach ($entity_types as $entity_type_name => $bundles) {
        foreach ($bundles as $bundle_name => $bundle) {
          // If this is not given that means that the user didn't change anything here (didn't click "edit"). So then
          // we simply keep the existing values, i.e. nothing to do here.
          if (!isset($submitted[$entity_type_name][$bundle_name]['handler'])) {
            continue;
          }

          $bundle_settings = $submitted[$entity_type_name][$bundle_name];

          // We handle field config outside of this array (same level as bundle config).
          $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler'] = $bundle_settings['handler'];
          if (isset($bundle_settings['handler_settings'])) {
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler_settings'] = $bundle_settings['handler_settings'];
          }
          else {
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler_settings'] = [];
          }
          if (!empty($bundle_settings['export'])) {
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['export'] = $bundle_settings['export'][$entity_type_name . '.' . $bundle_name]['export'];
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['export_pools'] = $bundle_settings['export'][$entity_type_name . '.' . $bundle_name]['export_pools'];
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['preview'] = $bundle_settings['export'][$entity_type_name . '.' . $bundle_name]['preview'];
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['pool_export_widget_type'] = $bundle_settings['export'][$entity_type_name . '.' . $bundle_name]['pool_export_widget_type'];
            if (isset($bundle_settings['export'][$entity_type_name . '.' . $bundle_name]['export_deletion_settings']['export_deletion'])) {
              $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['export_deletion_settings']['export_deletion'] = $bundle_settings['export'][$entity_type_name . '.' . $bundle_name]['export_deletion_settings']['export_deletion'];
            }
            else {
              $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['export_deletion_settings']['export_deletion'] = 0;
            }
          }
          else {
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['export'] = PushIntent::PUSH_DISABLED;
          }

          if (!empty($bundle_settings['import'])) {
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import'] = $bundle_settings['import'][$entity_type_name . '.' . $bundle_name]['import'];
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import_pools'] = $bundle_settings['import'][$entity_type_name . '.' . $bundle_name]['import_pools'];
            if (isset($bundle_settings['import'][$entity_type_name . '.' . $bundle_name]['import_deletion_settings']['import_deletion'])) {
              $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import_deletion_settings']['import_deletion'] = $bundle_settings['import'][$entity_type_name . '.' . $bundle_name]['import_deletion_settings']['import_deletion'];
            }
            else {
              $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import_deletion_settings']['import_deletion'] = 0;
            }
            if (isset($bundle_settings['import'][$entity_type_name . '.' . $bundle_name]['import_deletion_settings']['allow_local_deletion_of_import'])) {
              $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import_deletion_settings']['allow_local_deletion_of_import'] = $bundle_settings['import'][$entity_type_name . '.' . $bundle_name]['import_deletion_settings']['allow_local_deletion_of_import'];
            }
            else {
              $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import_deletion_settings']['allow_local_deletion_of_import'] = 0;
            }
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import_updates'] = $bundle_settings['import'][$entity_type_name . '.' . $bundle_name]['import_updates'];
          }
          else {
            $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import'] = PullIntent::PULL_DISABLED;
          }

          // If the user set this to disabled, remove all associated configuration.
          if ('ignore' === $bundle_settings['handler']) {
            // Remove field configuration completely for this.
            unset($result['per_bundle_settings'][$entity_type_name][$bundle_name]['properties']);
          }
          else {
            if (EntityHandlerPluginManager::isEntityTypeFieldable($entity_type_name)) {
              /**
               * @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields
               */
              $fields = $this->entityFieldManager->getFieldDefinitions($entity_type_name, $bundle_name);

              // @todo Test that the field config is in the right format (so as before).
              foreach ($bundle_settings['properties'] as $field_name => $settings) {
                if ('advanced' === $field_name) {
                  continue;
                }

                if (empty($settings['handler'])) {
                  $result['per_bundle_settings'][$entity_type_name][$bundle_name]['properties'][$field_name] = [
                    'handler' => 'ignore',
                    'export' => PushIntent::PUSH_DISABLED,
                    'import' => PullIntent::PULL_DISABLED,
                  ];

                  continue;
                }

                if (isset($settings['handler_settings']['subscribe_only_to'])) {
                  // @todo This should be handled by the Handler itself with another callback for saving / altering.
                  if (!empty($settings['handler_settings']['subscribe_only_to'])) {
                    /**
                     * @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields
                     */
                    $field_definition = $this->entityFieldManager->getFieldDefinitions($entity_type_name, $bundle_name)[$field_name];

                    $type = $field_definition->getSetting('target_type');
                    $storage = \Drupal::entityTypeManager()->getStorage($type);

                    foreach ($settings['handler_settings']['subscribe_only_to'] as $i => $ref) {
                      $entity = $storage->load($ref['target_id']);

                      $settings['handler_settings']['subscribe_only_to'][$i] = [
                        'type' => $entity->getEntityTypeId(),
                        'bundle' => $entity->bundle(),
                        'uuid' => $entity->uuid(),
                      ];
                    }
                  }
                }

                $result['per_bundle_settings'][$entity_type_name][$bundle_name]['properties'][$field_name] = $settings;
              }

              $handler = $this->entityPluginManager->createInstance($result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler'], [
                'entity_type_name' => $entity_type_name,
                'bundle_name' => $bundle_name,
                'settings' => $result['per_bundle_settings'][$entity_type_name][$bundle_name]['settings'],
                'sync' => NULL,
              ]);

              $forbidden_fields = $handler->getForbiddenFields();

              $pools = Pool::getAll();
              if (count($pools)) {
                $reserved = reset($pools)
                  ->getClient()
                  ->getReservedPropertyNames();
                $forbidden_fields = array_merge($forbidden_fields, $reserved);
              }

              foreach ($fields as $field_name => $field) {
                if (isset($bundle_settings['properties'][$field_name])) {
                  continue;
                }

                $field_handlers = $this->fieldPluginManager->getHandlerOptions($entity_type_name, $bundle_name, $field_name, $field, TRUE);
                if (empty($field_handlers) || in_array($field_name, $forbidden_fields)) {
                  $handler_id = 'ignore';

                  $result['per_bundle_settings'][$entity_type_name][$bundle_name]['properties'][$field_name] = [
                    'handler' => $handler_id,
                    'handler_settings' => [],
                    'export' => PushIntent::PUSH_DISABLED,
                    'import' => PullIntent::PULL_DISABLED,
                  ];
                }
                else {
                  reset($field_handlers);
                  $handler_id = key($field_handlers);

                  $result['per_bundle_settings'][$entity_type_name][$bundle_name]['properties'][$field_name] = [
                    'handler' => $handler_id,
                    'handler_settings' => [],
                    'export' => PushIntent::PUSH_AUTOMATICALLY,
                    'import' => PullIntent::PULL_AUTOMATICALLY,
                  ];
                }
              }
            }
          }
        }
      }
    }

    if (!isset($result['per_bundle_settings'])) {
      $result['per_bundle_settings'] = [];
    }

    foreach ($result['per_bundle_settings'] as $entity_type_name => $bundles) {
      foreach ($bundles as $bundle_name => $bundle_config) {
        // Bundle was deleted => Remove entry from the form values array.
        if (empty($entity_types[$entity_type_name][$bundle_name])) {
          unset($result['per_bundle_settings'][$entity_type_name][$bundle_name]);

          continue;
        }
        if (!empty($bundle_config['properties'])) {
          foreach ($bundle_config['properties'] as $field_name => $field_config) {
            /**
             * @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields
             */
            $fields = $this->entityFieldManager->getFieldDefinitions($entity_type_name, $bundle_name);

            // Field was removed => Remove entry from the form values array.
            if (empty($fields[$field_name])) {
              unset($result['per_bundle_settings'][$entity_type_name][$bundle_name]['properties'][$field_name]);

              continue;
            }
          }
        }
      }
    }

    return $result;
  }

  /**
   * Render the bundle edit form.
   *
   * @param $entity_type_name
   * @param $bundle_name
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *
   * @return array
   */
  protected function renderEnabledBundle(FormStateInterface $form_state, $entity_type_name, $bundle_name) {
    $entity_types = $this->bundleInfoService->getAllBundleInfo();

    $entity_bundle = $entity_types[$entity_type_name][$bundle_name];

    $settings_id = 'per-bundle-settings---' . $entity_type_name . '---' . $bundle_name;

    $type = $this->getCurrentFormType();
    $allow_push = Flow::TYPE_PUSH === $type || Flow::TYPE_BOTH === $type;
    $allow_pull = Flow::TYPE_PULL === $type || Flow::TYPE_BOTH === $type;

    $current_values = $this->getCurrentValues($form_state);

    // Before a flow can be created, at least one pool must exist.
    // Get all pool entities.
    $pool_entities = Pool::getAll();

    $push_option_labels = [
      PushIntent::PUSH_DISABLED => $this->t('Disabled')->render(),
      PushIntent::PUSH_AUTOMATICALLY => $this->t('All')->render(),
      PushIntent::PUSH_AS_DEPENDENCY => $this->t('Referenced')->render(),
      PushIntent::PUSH_MANUALLY => $this->t('Manually')->render(),
    ];

    $pull_option_labels = [
      PullIntent::PULL_DISABLED => $this->t('Disabled')->render(),
      PullIntent::PULL_AUTOMATICALLY => $this->t('All')->render(),
      PullIntent::PULL_AS_DEPENDENCY => $this->t('Referenced')->render(),
      PullIntent::PULL_MANUALLY => $this->t('Manually')->render(),
    ];

    $def_per_bundle_settings = $this->getCurrentValues($form_state)['per_bundle_settings'];

    $display_modes = $this->entityTypeManager
      ->getStorage('entity_view_display')
      ->loadMultiple();

    $display_modes_ids = array_keys($display_modes);

    $version = Flow::getEntityTypeVersion($entity_type_name, $bundle_name);

    $entity_push_bundle_row = [];

    $available_preview_modes = [];
    foreach ($display_modes_ids as $id) {
      $length = strlen($entity_type_name) + strlen($bundle_name) + 2;
      if (substr($id, 0, $length) != $entity_type_name . '.' . $bundle_name . '.') {
        continue;
      }
      $id = substr($id, $length);
      $label = $id;
      $available_preview_modes[$id] = $label;
    }

    if (!isset($def_per_bundle_settings[$entity_type_name][$bundle_name])) {
      $row_default_values = [
        'id' => $entity_type_name . '---' . $bundle_name,
        'export' => $allow_push ? ('node' === $entity_type_name ? PushIntent::PUSH_AUTOMATICALLY : PushIntent::PUSH_AS_DEPENDENCY) : NULL,
        'export_deletion_settings' => [
          'export_deletion' => TRUE,
        ],
        'import' => $allow_pull ? ('node' === $entity_type_name ? PullIntent::PULL_AUTOMATICALLY : PullIntent::PULL_AS_DEPENDENCY) : NULL,
        'import_deletion_settings' => [
          'import_deletion' => TRUE,
          'allow_local_deletion_of_import' => FALSE,
        ],
        'handler_settings' => [],
        'import_updates' => PullIntent::PULL_UPDATE_FORCE,
        'preview' => Flow::PREVIEW_DISABLED,
        'display_name' => $this->t('@bundle', [
          '@bundle' => $entity_bundle['label'],
        ])->render(),
        'entity_type' => $entity_type_name,
        'entity_bundle' => $bundle_name,
        'pool_export_widget_type' => 'checkboxes',
      ];
      foreach ($pool_entities as $pool) {
        $row_default_values['export_pools'][$pool->id()] = PushIntent::PUSH_AUTOMATICALLY === $row_default_values['export'] ? Pool::POOL_USAGE_FORCE : Pool::POOL_USAGE_ALLOW;
        $row_default_values['import_pools'][$pool->id()] = Pool::POOL_USAGE_FORCE;
      }
    }
    else {
      $row_default_values = $def_per_bundle_settings[$entity_type_name][$bundle_name]['settings'];

      if (empty($row_default_values['export']) || PushIntent::PUSH_DISABLED === $row_default_values['export']) {
        if (!$allow_pull) {
          $row_default_values['export'] = 'node' === $entity_type_name ? PushIntent::PUSH_AUTOMATICALLY : PushIntent::PUSH_AS_DEPENDENCY;
        }
      }

      if (empty($row_default_values['import']) || PullIntent::PULL_DISABLED === $row_default_values['import']) {
        if (!$allow_push) {
          $row_default_values['import'] = 'node' === $entity_type_name ? PullIntent::PULL_AUTOMATICALLY : PullIntent::PULL_AS_DEPENDENCY;
        }
      }
    }

    $entity_handlers = $this->entityPluginManager->getHandlerOptions($entity_type_name, $bundle_name, TRUE);
    $entity_handler_names = array_keys($entity_handlers);
    $handler_id = empty($row_default_values['handler']) || 'ignore' === $row_default_values['handler'] ?
      reset($entity_handler_names) :
      $row_default_values['handler'];

    $bundle_info = [
      '#type' => 'details',
      '#prefix' => '<div id="' . $settings_id . '">',
      '#suffix' => '</div>',
      '#open' => TRUE,
      '#title' => $this->t('@bundle (@machine_name)', [
        '@bundle' => $entity_bundle['label'],
        '@machine_name' => $bundle_name,
      ]),
      'version' => [
        '#markup' => '<small>Entity type - bundle version: ' . $version . '</small>' .
        (empty($row_default_values['version']) || $version == $row_default_values['version'] ? '' : '<br><strong>Changed from ' . $row_default_values['version'] . '</strong>'),
      ],
    ];

    $bundle_info['edit'] = [
      '#type' => 'hidden',
      '#value' => '1',
    ];

    $bundle_info['handler'] = [
      '#type' => 'select',
      '#title' => $this->t('Handler'),
      '#title_display' => 'invisible',
      '#options' => $entity_handlers,
      '#default_value' => $handler_id,
    ];

    /**
     * @var \Drupal\cms_content_sync\Plugin\EntityHandlerInterface $handler
     */
    $handler = $this->entityPluginManager->createInstance($handler_id, [
      'entity_type_name' => $entity_type_name,
      'bundle_name' => $bundle_name,
      'settings' => $row_default_values,
      'sync' => NULL,
    ]);

    $allowed_push_options = $handler->getAllowedPushOptions();
    $push_options = [];
    foreach ($allowed_push_options as $option) {
      $push_options[$option] = $push_option_labels[$option];
    }

    if (!$allow_pull) {
      unset($push_options[PushIntent::PUSH_DISABLED]);
    }

    $bundle_info['disable'] = [
      '#type' => 'submit',
      '#value' => $this->t('Disable'),
      '#name' => 'disable-' . $entity_type_name . '-' . $bundle_name,
      '#submit' => ['::disableBundle'],
      '#entity_type' => $entity_type_name,
      '#bundle' => $bundle_name,
      '#limit_validation_errors' => [],
      '#attributes' => [
        'class' => ['button--danger'],
      ],
      '#ajax' => [
        'callback' => '::ajaxReturn',
        'wrapper' => $settings_id,
        'method' => 'replace',
        'effect' => 'fade',
        'progress' => [
          'type' => 'throbber',
          'message' => 'loading settings...',
        ],
      ],
    ];

    if ('1' === $form_state->getValue([
      $entity_type_name,
      $bundle_name,
      'show-version-mismatches',
    ])) {
      $bundle_info['version_mismatch_' . $entity_type_name . '_' . $bundle_name] = _cms_content_sync_display_version_mismatches(NULL, $form_state);
    }
    else {
      $bundle_info['version_mismatch_' . $entity_type_name . '_' . $bundle_name] = [
        '#type' => 'submit',
        '#value' => t('Show version mismatches'),
        '#name' => 'version-' . $entity_type_name . '-' . $bundle_name,
        '#submit' => ['::showVersionMismatches'],
        '#entity_type' => $entity_type_name,
        '#bundle' => $bundle_name,
        '#recursive' => FALSE,
        '#limit_validation_errors' => [],
        '#ajax' => [
          'callback' => '::ajaxReturn',
          'wrapper' => $settings_id,
          'method' => 'replace',
          'effect' => 'fade',
          'progress' => [
            'type' => 'throbber',
            'message' => 'loading settings...',
          ],
        ],
      ];
    }

    if ('ignore' != $handler_id) {
      $advanced_settings = $handler->getHandlerSettings($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler_settings'] ?? [], $this->getCurrentFormType());
      if (count($advanced_settings)) {
        $bundle_info['handler_settings'] = array_merge([
          '#type' => 'container',
        ], $advanced_settings);
      }
    }

    if (!isset($bundle_info['handler_settings'])) {
      $bundle_info['handler_settings'] = [
        '#markup' => '<br><br>',
      ];
    }

    if ($allow_push) {
      $entity_push_bundle_row['export'] = [
        '#type' => 'select',
        '#title' => $this->t('Push'),
        '#title_display' => 'invisible',
        '#options' => $push_options,
        '#default_value' => $row_default_values['export'],
      ];

      foreach ($pool_entities as $pool) {
        $entity_push_bundle_row['export_pools'][$pool->id()] = [
          '#type' => 'select',
          '#title' => $this->t($pool->label()),
          '#options' => [
            Pool::POOL_USAGE_FORCE => $this->t('Force'),
            Pool::POOL_USAGE_ALLOW => $this->t('Allow'),
            Pool::POOL_USAGE_FORBID => $this->t('Forbid'),
          ],
          '#default_value' => $row_default_values['export_pools'][$pool->id()] ?? (
                  $this->entity->id()
                  ? Pool::POOL_USAGE_FORBID
                  : (
                      PushIntent::PUSH_AUTOMATICALLY === $row_default_values['export']
                  ? Pool::POOL_USAGE_FORCE
                  : Pool::POOL_USAGE_ALLOW
                  )
          ),
        ];
      }

      $entity_push_bundle_row['export_deletion_settings']['export_deletion'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Push deletion'),
        '#default_value' => isset($row_default_values['export_deletion_settings']['export_deletion']) && 1 == $row_default_values['export_deletion_settings']['export_deletion'],
      ];

      $entity_push_bundle_row['pool_export_widget_type'] = [
        '#type' => 'select',
        '#options' => [
          'checkboxes' => $this->t('Checkboxes'),
          'radios' => $this->t('Radio boxes'),
          'single_select' => $this->t('Single select'),
          'multi_select' => $this->t('Multi select'),
        ],
        '#default_value' => $row_default_values['pool_export_widget_type'] ?? 'checkboxes',
      ];

      $options = array_merge([
        Flow::PREVIEW_DISABLED => $this->t('Disabled')->render(),
      ], 'ignore' == $handler_id ? [] : array_merge([
        Flow::PREVIEW_TABLE => $this->t('Default')
          ->render(),
      ], $available_preview_modes));
      $default = 'ignore' == $handler_id ? Flow::PREVIEW_DISABLED : Flow::PREVIEW_TABLE;
      $entity_push_bundle_row['preview'] = [
        '#type' => 'select',
        '#title' => $this->t('Preview'),
        '#title_display' => 'invisible',
        '#options' => $options,
        '#default_value' => isset($row_default_values['preview']) || 'ignore' == $handler_id ? $row_default_values['preview'] : $default,
        '#description' => $this->t('Make sure to go to the general "Settings" and enable previews to make use of this.'),
      ];

      $entity_push_table = [
        '#type' => 'table',
        '#sticky' => TRUE,
        '#header' => [
          $this->t('Push'),
          $this->t('Push to pools'),
          $this->t('Push deletions'),
          $this->t('Pool widget'),
          $this->t('Preview'),
        ],
        $entity_type_name . '.' . $bundle_name => $entity_push_bundle_row,
      ];
    }

    if ($allow_pull) {
      $allowed_pull_options = $handler->getAllowedPullOptions();
      $pull_options = [];
      foreach ($allowed_pull_options as $option) {
        $pull_options[$option] = $pull_option_labels[$option];
      }

      if (!$allow_push) {
        unset($pull_options[PullIntent::PULL_DISABLED]);
      }

      $entity_pull_bundle_row['import'] = [
        '#type' => 'select',
        '#title' => $this->t('Pull'),
        '#title_display' => 'invisible',
        '#options' => $pull_options,
        '#default_value' => $row_default_values['import'],
      ];

      foreach ($pool_entities as $pool) {
        $entity_pull_bundle_row['import_pools'][$pool->id()] = [
          '#type' => 'select',
          '#title' => $this->t($pool->label()),
          '#options' => [
            Pool::POOL_USAGE_FORCE => $this->t('Force'),
            Pool::POOL_USAGE_ALLOW => $this->t('Allow'),
            Pool::POOL_USAGE_FORBID => $this->t('Forbid'),
          ],
          '#default_value' => $row_default_values['import_pools'][$pool->id()] ?? (
                  $this->entity->id()
                  ? Pool::POOL_USAGE_FORBID
                  : Pool::POOL_USAGE_FORCE
          ),
        ];
      }

      $entity_pull_bundle_row['import_deletion_settings']['import_deletion'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Pull deletion'),
        '#default_value' => isset($row_default_values['import_deletion_settings']['import_deletion']) && 1 == $row_default_values['import_deletion_settings']['import_deletion'],
      ];

      $entity_pull_bundle_row['import_deletion_settings']['allow_local_deletion_of_import'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Allow deletion of pulled content'),
        '#default_value' => isset($row_default_values['import_deletion_settings']['allow_local_deletion_of_import']) && 1 == $row_default_values['import_deletion_settings']['allow_local_deletion_of_import'],
      ];

      $entity_pull_bundle_row['import_updates'] = [
        '#type' => 'select',
        '#options' => [
          PullIntent::PULL_UPDATE_FORCE => $this->t('Dismiss local changes'),
          PullIntent::PULL_UPDATE_IGNORE => $this->t('Ignore updates completely'),
          PullIntent::PULL_UPDATE_FORCE_AND_FORBID_EDITING => $this->t('Forbid local changes and update'),
          PullIntent::PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN => $this->t('Update unless overwritten locally'),
        ],
        '#default_value' => $row_default_values['import_updates'] ?? NULL,
      ];

      if ('node' === $entity_type_name) {
        $bundle_type = $this->entityTypeManager->getStorage('node_type')
          ->load($bundle_name);
        if ($bundle_type instanceof RevisionableEntityBundleInterface && $bundle_type->shouldCreateNewRevision()) {
          $entity_pull_bundle_row['import_updates']['#options'][PullIntent::PULL_UPDATE_UNPUBLISHED] = $this->t('Create unpublished revisions');
        }
      }

      $entity_pull_table = [
        '#type' => 'table',
        '#sticky' => TRUE,
        '#header' => [
          $this->t('Pull'),
          $this->t('Pull from pool'),
          $this->t('Pull deletions'),
          $this->t('Pull updates'),
        ],
        $entity_type_name . '.' . $bundle_name => $entity_pull_bundle_row,
      ];
    }

    $show_all_fields = $form_state->getValue([
      $entity_type_name,
      $bundle_name,
      'properties',
      'advanced',
      'show-all',
    ]);

    $entity_field_table = $this->renderFields($form_state, $entity_type_name, $bundle_name, '1' === $show_all_fields);

    if (isset($entity_push_table)) {
      $bundle_info['export'] = $entity_push_table;
    }

    if (isset($entity_pull_table)) {
      $bundle_info['import'] = $entity_pull_table;
    }

    if (isset($entity_field_table)) {
      $bundle_info['properties'] = $entity_field_table;
    }

    return $bundle_info;
  }

  /**
   * Render the fields of the given entity type; either all of them or only
   * those that either have:
   * - advanced handler settings available
   * - a handler that was intentionally disabled.
   *
   * @param $entity_type_name
   * @param $bundle_name
   * @param bool $expanded
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   *
   * @return array
   */
  protected function renderFields(FormStateInterface $form_state, $entity_type_name, $bundle_name, $expanded = FALSE) {
    $field_settings_id = 'per-bundle-settings---' . $entity_type_name . '---' . $bundle_name . '---properties';

    $form_type = $this->getCurrentFormType();
    $allow_push = Flow::TYPE_PUSH === $form_type || Flow::TYPE_BOTH === $form_type;
    $allow_pull = Flow::TYPE_PULL === $form_type || Flow::TYPE_BOTH === $form_type;

    $current_values = $this->getCurrentValues($form_state);

    $push_option_labels_fields = [
      PushIntent::PUSH_DISABLED => $this->t('No')->render(),
      PushIntent::PUSH_AUTOMATICALLY => $this->t('Yes')->render(),
    ];

    $pull_option_labels_fields = [
      PullIntent::PULL_DISABLED => $this->t('No')->render(),
      PullIntent::PULL_AUTOMATICALLY => $this->t('Yes')->render(),
    ];

    $field_map = $this->entityFieldManager->getFieldMap();

    $def_per_bundle_settings = $this->getCurrentValues($form_state)['per_bundle_settings'];

    $entity_handlers = $this->entityPluginManager->getHandlerOptions($entity_type_name, $bundle_name, TRUE);
    $entity_handler_names = array_keys($entity_handlers);
    $handler_id = empty($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler']) || 'ignore' === $current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler'] ?
        reset($entity_handler_names) :
        $current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['handler'];

    $handler = $this->entityPluginManager->createInstance($handler_id, [
      'entity_type_name' => $entity_type_name,
      'bundle_name' => $bundle_name,
      'settings' => empty($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']) ? [] : $current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings'],
      'sync' => NULL,
    ]);

    if (isset($field_map[$entity_type_name])) {
      $entity_field_table = [
        '#type' => 'table',
        '#prefix' => '<div id="' . $field_settings_id . '"><h3>' . $this->t('Fields') . '</h3>',
        '#suffix' => '</div>',
        '#header' => array_merge([
          $this->t('Name'),
          $this->t('Handler'),
          $this->t('Handler settings'),
        ], $allow_push && Flow::TYPE_BOTH === !$form_type ? [
          $this->t('Push'),
        ] : [], $allow_pull && Flow::TYPE_BOTH === !$form_type ? [
          $this->t('Pull'),
        ] : []),
      ];

      $forbidden_fields = $handler->getForbiddenFields();
      $pools = Pool::getAll();
      if (count($pools)) {
        $reserved = reset($pools)
          ->getClient()
          ->getReservedPropertyNames();
        $forbidden_fields = array_merge($forbidden_fields, $reserved);
      }

      $entityFieldManager = $this->entityFieldManager;
      /**
       * @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields
       */
      $fields = $entityFieldManager->getFieldDefinitions($entity_type_name, $bundle_name);
      foreach ($fields as $field_name => $field) {
        $field_row = [];

        $title = $field_name;
        $referenced_type = NULL;
        $referenced_bundles = 'all';
        $bundles = '';
        $show_enable_all = FALSE;
        if (in_array($field->getType(), [
          'entity_reference',
          'entity_reference_revisions',
          'cohesion_entity_reference_revisions',
          'webform',
        ])) {
          $referenced_type = $field->getSetting('target_type');
        }
        elseif (in_array($field->getType(), ['image', 'file', 'file_uri'])) {
          $referenced_type = 'file';
        }
        elseif (in_array($field->getType(), ['bricks'])) {
          $referenced_type = 'brick';
        }

        $field_settings = $field->getSettings();
        if ((!empty($field_settings['handler_settings']) || 'brick' === $referenced_type) && !empty($field_settings['handler_settings']['target_bundles'])) {
          $bundles .= '<ul>';

          /** @var \Drupal\cms_content_sync\Helper\FieldHelper $field_helper */
          $field_helper = \Drupal::service('cms_content_sync.field_helper');
          $target_bundles = $field_helper->getEntityReferenceFieldAllowedTargetBundles($field);

          $referenced_bundles = [];

          foreach ($target_bundles as $bundle) {
            $bundles .= '<li>' . $bundle . '</li>';

            $referenced_bundles[] = $bundle;

            if (empty($current_values['per_bundle_settings'][$referenced_type][$bundle]['settings']['handler']) || Flow::HANDLER_IGNORE == $current_values['per_bundle_settings'][$referenced_type][$bundle]['settings']['handler']) {
              if ('1' != $form_state->getValue([
                $referenced_type,
                $bundle,
                'edit',
              ])) {
                $entity_handlers = $this->entityPluginManager->getHandlerOptions($referenced_type, $bundle, TRUE);
                if (!empty($entity_handlers)) {
                  $show_enable_all = TRUE;
                }
              }
            }
          }
          $bundles .= '</ul>';
        }

        if ($referenced_type) {
          $title .= '<br><small>Reference to ' . $referenced_type;
          $title .= !empty($bundles) ? ':' . $bundles : '';
          $title .= '</small>';

          if (empty($bundles)) {
            $entity_types = $this->bundleInfoService->getAllBundleInfo();

            foreach ($entity_types[$referenced_type] as $bundle => $set) {
              if (empty($current_values['per_bundle_settings'][$referenced_type][$bundle]['settings']['handler']) || Flow::HANDLER_IGNORE == $current_values['per_bundle_settings'][$referenced_type][$bundle]['settings']['handler']) {
                if ('1' != $form_state->getValue([
                  $referenced_type,
                  $bundle,
                  'edit',
                ])) {
                  $entity_handlers = $this->entityPluginManager->getHandlerOptions($referenced_type, $bundle, TRUE);
                  if (!empty($entity_handlers)) {
                    $show_enable_all = TRUE;
                  }
                }

                break;
              }
            }
          }
        }

        $field_row['bundle'] = [
          '#markup' => $title,
        ];

        if ($show_enable_all) {
          $field_row['bundle']['enable-all'] = [
            '#type' => 'submit',
            '#value' => $this->t('Enable all'),
            '#name' => 'enable_all-' . $entity_type_name . '-' . $bundle_name . '-' . $field_name,
            '#submit' => ['::enableAllReferenced'],
            '#entity_type' => $entity_type_name,
            '#bundle' => $bundle_name,
            '#field' => $field_name,
            '#referenced_type' => $referenced_type,
            '#referenced_bundles' => $referenced_bundles,
            '#limit_validation_errors' => [],
            '#ajax' => [
              'callback' => '::enableAllReferencedReturn',
              'progress' => [
                'type' => 'throbber',
                'message' => 'loading settings...',
              ],
            ],
          ];
        }

        if (!isset($def_per_bundle_settings[$entity_type_name][$bundle_name]['properties'][$field_name])) {
          $field_default_values = [
            'id' => $field_name,
            'export' => NULL,
            'import' => NULL,
            'preview' => NULL,
            'entity_type' => $entity_type_name,
            'entity_bundle' => $bundle_name,
          ];
        }
        else {
          $field_default_values = $def_per_bundle_settings[$entity_type_name][$bundle_name]['properties'][$field_name];
        }

        if (FALSE !== in_array($field_name, $forbidden_fields)) {
          $handler_id = 'ignore';
          $field_handlers = [
            'ignore' => $this->t('Default')->render(),
          ];
        }
        else {
          $field_handlers = $this->fieldPluginManager->getHandlerOptions($entity_type_name, $bundle_name, $field_name, $field, TRUE);
          if (empty($field_handlers)) {
            $handler_id = 'ignore';
          }
          else {
            reset($field_handlers);
            $handler_id = empty($field_default_values['handler']) ? key($field_handlers) : $field_default_values['handler'];
          }
        }

        $options = count($field_handlers) ? ($field->isRequired() ? $field_handlers : array_merge([
          'ignore' => $this->t('Ignore')
            ->render(),
        ], $field_handlers)) : [
          'ignore' => $this->t('Not supported')->render(),
        ];

        $field_row['handler'] = [
          '#type' => 'select',
          '#title' => $this->t('Handler'),
          '#title_display' => 'invisible',
          '#options' => $options,
          '#disabled' => !count($field_handlers) || (1 == count($field_handlers) && isset($field_handlers['ignore'])),
          '#default_value' => $handler_id,
          '#limit_validation_errors' => [],
        ];

        if ('ignore' == $handler_id) {
          // Disabled means we don't syndicate it as a normal field handler. Instead, the entity handler will already take care of it as it's a required property.
          // But saying "no" will be confusing to users as it's actually syndicated. So we use DISABLED => YES for the naming.
          $push_options = [
            PushIntent::PUSH_DISABLED => $this->t('Yes')->render(),
          ];
        }
        else {
          /**
           * @var \Drupal\cms_content_sync\Plugin\FieldHandlerInterface $handler
           */
          $handler = $this->fieldPluginManager->createInstance($handler_id, [
            'entity_type_name' => $entity_type_name,
            'bundle_name' => $bundle_name,
            'field_name' => $field_name,
            'field_definition' => $field,
            'settings' => $field_default_values,
            'sync' => $this->entity,
          ]);

          $allowed_push_options = $handler->getAllowedPushOptions();
          $push_options = [];
          foreach ($allowed_push_options as $option) {
            $push_options[$option] = $push_option_labels_fields[$option];
          }
        }

        $field_row['handler_settings'] = ['#markup' => ''];

        if ('ignore' != $handler_id) {
          $advanced_settings = $handler->getHandlerSettings($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['properties'][$field_name]['handler_settings'] ?? [], $form_type);
          if (count($advanced_settings)) {
            $field_row['handler_settings'] = array_merge([
              '#type' => 'container',
            ], $advanced_settings);
          }
          elseif (!$expanded && !$referenced_type) {
            continue;
          }
        }
        elseif (!$expanded && 1 === count($options) && !$referenced_type) {
          continue;
        }

        if ($allow_push) {
          $field_row['export'] = [
            '#type' => 'select',
            '#title' => $this->t('Push'),
            '#title_display' => 'invisible',
            '#disabled' => count($push_options) < 2,
            '#options' => $push_options,
            '#default_value' => $field_default_values['export'] ? $field_default_values['export'] : (isset($push_options[PushIntent::PUSH_AUTOMATICALLY]) ? PushIntent::PUSH_AUTOMATICALLY : NULL),
            '#access' => Flow::TYPE_BOTH === $form_type ? TRUE : FALSE,
          ];
        }

        if ('ignore' == $handler_id) {
          // Disabled means we don't syndicate it as a normal field handler. Instead, the entity handler will already take care of it as it's a required property.
          // But saying "no" will be confusing to users as it's actually syndicated. So we use DISABLED => YES for the naming.
          $pull_options = [
            PullIntent::PULL_DISABLED => $this->t('Yes')->render(),
          ];
        }
        else {
          $allowed_pull_options = $handler->getAllowedPullOptions();
          $pull_options = [];
          foreach ($allowed_pull_options as $option) {
            $pull_options[$option] = $pull_option_labels_fields[$option];
          }
        }

        if ($allow_pull) {
          $field_row['import'] = [
            '#type' => 'select',
            '#title' => $this->t('Pull'),
            '#title_display' => 'invisible',
            '#options' => $pull_options,
            '#disabled' => count($pull_options) < 2,
            '#default_value' => !empty($field_default_values['import']) ? $field_default_values['import'] : (isset($pull_options[PullIntent::PULL_AUTOMATICALLY]) ? PullIntent::PULL_AUTOMATICALLY : NULL),
            '#access' => Flow::TYPE_BOTH === $form_type ? TRUE : FALSE,
          ];
        }

        $entity_field_table[$field_name] = $field_row;
      }

      if (!$expanded) {
        $entity_field_table['advanced']['show-all-action'] = [
          '#type' => 'submit',
          '#value' => $this->t('Show all fields'),
          '#name' => 'show_all-' . $entity_type_name . '-' . $bundle_name,
          '#submit' => ['::showAllFields'],
          '#entity_type' => $entity_type_name,
          '#bundle' => $bundle_name,
          '#limit_validation_errors' => [],
          '#ajax' => [
            'callback' => '::ajaxReturn',
            'wrapper' => $field_settings_id,
            'method' => 'replace',
            'effect' => 'fade',
            'progress' => [
              'type' => 'throbber',
              'message' => 'loading settings...',
            ],
          ],
        ];
      }

      $entity_field_table['advanced']['show-all'] = [
        '#type' => 'hidden',
        '#value' => $expanded ? '1' : '0',
      ];

      return $entity_field_table;
    }
  }

  /**
   * The bundle isn't pushed or pulled right now. The user can enable it with a
   * button then.
   *
   * @param $entity_type_name
   * @param $bundle_name
   *
   * @return array
   */
  protected function renderDisabledBundle(FormStateInterface $form_state, $entity_type_name, $bundle_name) {
    $entity_types = $this->bundleInfoService->getAllBundleInfo();

    $entity_bundle = $entity_types[$entity_type_name][$bundle_name];

    $settings_id = 'per-bundle-settings---' . $entity_type_name . '---' . $bundle_name;

    $bundle_info = [
      '#prefix' => '<div id="' . $settings_id . '">',
      '#suffix' => '</div>',
      '#markup' => '<h2>' . $this->t('@bundle (@machine_name)', [
        '@bundle' => $entity_bundle['label'],
        '@machine_name' => $bundle_name,
      ]) . '</h2>',
    ];

    $entity_handlers = $this->entityPluginManager->getHandlerOptions($entity_type_name, $bundle_name, TRUE);
    if (empty($entity_handlers)) {
      $bundle_info['#markup'] .= '<p>This entity type / bundle is not supported.</p>';
    }
    else {
      $bundle_info['handler'] = [
        '#type' => 'hidden',
        '#value' => 'ignore',
      ];

      $bundle_info['edit'] = [
        '#type' => 'hidden',
        '#value' => '0',
      ];

      $title = $this->t('Enable');

      $bundle_info['enable'] = [
        '#type' => 'submit',
        '#value' => $title,
        '#name' => 'enable-' . $entity_type_name . '-' . $bundle_name,
        '#submit' => ['::enableBundle'],
        '#entity_type' => $entity_type_name,
        '#bundle' => $bundle_name,
        '#limit_validation_errors' => [],
        '#attributes' => [
          'class' => ['button--primary'],
        ],
        '#ajax' => [
          'callback' => '::ajaxReturn',
          'wrapper' => $settings_id,
          'method' => 'replace',
          'effect' => 'fade',
          'progress' => [
            'type' => 'throbber',
            'message' => 'loading settings...',
          ],
        ],
      ];
    }

    return $bundle_info;
  }

  /**
   * Bundle has settings already, but the user is editing the Flow so by
   * default we don't show all bundle edit forms as open but hide them all to
   * save space and make the form faster. The user can then click Edit to
   * change settings.
   *
   * @param $entity_type_name
   * @param $bundle_name
   *
   * @return array
   */
  protected function renderBundleSummary(FormStateInterface $form_state, $entity_type_name, $bundle_name) {
    $entity_types = $this->bundleInfoService->getAllBundleInfo();

    $entity_bundle = $entity_types[$entity_type_name][$bundle_name];

    $settings_id = 'per-bundle-settings---' . $entity_type_name . '---' . $bundle_name;

    $current_values = $this->getCurrentValues($form_state);

    $push_option_labels = [
      PushIntent::PUSH_DISABLED => $this->t('Disabled')->render(),
      PushIntent::PUSH_AUTOMATICALLY => $this->t('All')->render(),
      PushIntent::PUSH_AS_DEPENDENCY => $this->t('Referenced')->render(),
      PushIntent::PUSH_MANUALLY => $this->t('Manually')->render(),
    ];

    $pull_option_labels = [
      PullIntent::PULL_DISABLED => $this->t('Disabled')->render(),
      PullIntent::PULL_AUTOMATICALLY => $this->t('All')->render(),
      PullIntent::PULL_AS_DEPENDENCY => $this->t('Referenced')->render(),
      PullIntent::PULL_MANUALLY => $this->t('Manually')->render(),
    ];

    $bundle_info = [
      '#prefix' => '<div id="' . $settings_id . '">',
      '#suffix' => '</div>',
      '#markup' => '<h2>' . $this->t('@bundle (@machine_name)', [
        '@bundle' => $entity_bundle['label'],
        '@machine_name' => $bundle_name,
      ]) . '</h2>',
    ];

    $entity_handlers = $this->entityPluginManager->getHandlerOptions($entity_type_name, $bundle_name, TRUE);
    if (empty($entity_handlers)) {
      $bundle_info['#markup'] .= '<p>This entity type / bundle is not supported.</p>';
    }
    else {
      $does_push = isset($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['export']) && PushIntent::PUSH_DISABLED !== $current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['export'];
      $does_pull = isset($current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import']) && PullIntent::PULL_DISABLED !== $current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import'];
      $push_label = $does_push ? 'Push ' . $push_option_labels[$current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['export']] : NULL;
      $pull_label = $does_pull ? 'Pull ' . $pull_option_labels[$current_values['per_bundle_settings'][$entity_type_name][$bundle_name]['settings']['import']] : NULL;
      $bundle_info['summary'] = [
        '#markup' => $push_label ? (
                $pull_label ? $push_label . ' and ' . $pull_label : $push_label
        ) : (
                $does_pull ? $pull_label : 'Not configured'
        ),
      ];

      $title = $this->t('Edit');

      $bundle_info['edit'] = [
        '#type' => 'hidden',
        '#value' => '0',
      ];

      $bundle_info['enable'] = [
        '#type' => 'submit',
        '#submit' => ['::enableBundle'],
        '#value' => $title,
        '#name' => 'enable-' . $entity_type_name . '-' . $bundle_name,
        '#entity_type' => $entity_type_name,
        '#bundle' => $bundle_name,
        '#limit_validation_errors' => [],
        '#ajax' => [
          'callback' => '::ajaxReturn',
          'wrapper' => $settings_id,
          'method' => 'replace',
          'effect' => 'fade',
          'progress' => [
            'type' => 'throbber',
            'message' => 'loading settings...',
          ],
        ],
      ];
    }

    return $bundle_info;
  }

  /**
   * AJAX requests will make the form forget the 'edit' status of the bundles,
   * thus their form elements will disappear in the render array (*not in the
   * UI though*), so even though the user still sees them correctly, changes
   * will just not be saved.
   *
   * @param $form
   */
  protected function fixMissingFormStateFromAjax($form, FormStateInterface $form_state) {
    foreach ($form as $entity_type_name => $bundle_elements) {
      if (!is_array($bundle_elements)) {
        continue;
      }

      foreach ($bundle_elements as $bundle_name => $elements) {
        if (!is_array($elements)) {
          continue;
        }

        if (isset($elements['edit']) && is_array($elements['edit'])) {
          if (isset($_POST[$entity_type_name][$bundle_name]['edit']) && '1' === $_POST[$entity_type_name][$bundle_name]['edit']) {
            $form_state->setValue([
              $entity_type_name,
              $bundle_name,
              'edit',
            ], '1');
          }
        }

        if (isset($elements['properties']) && is_array($elements['properties'])) {
          if (isset($_POST[$entity_type_name][$bundle_name]['properties']['advanced']['show-all']) && '1' === $_POST[$entity_type_name][$bundle_name]['properties']['advanced']['show-all']) {
            $form_state->setValue([
              $entity_type_name,
              $bundle_name,
              'properties',
              'advanced',
              'show-all',
            ], '1');
          }
        }
      }
    }
  }

  /**
   * Should we display this bundle open or not?
   *
   * @param $entity_type_name
   * @param $bundle_name
   *
   * @return bool
   */
  protected function isBundleOpen(FormStateInterface $form_state, $entity_type_name, $bundle_name) {
    $values = $form_state->getValues();

    return isset($values[$entity_type_name][$bundle_name]['handler']) && 'ignore' !== $values[$entity_type_name][$bundle_name]['handler'];
  }

  /**
   * {@inheritdoc}
   */
  protected function actions(array $form, FormStateInterface $form_state) {
    if (!$this->getCurrentFormType()) {
      return [];
    }

    $element = parent::actions($form, $form_state);
    $element['submit']['#value'] = $this->t('Save and export');
    $element['save_without_export'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save without export'),
      '#submit' => ['::submitForm', '::save'],
    ];

    return $element;
  }

  /**
   * Disable form elements which are overridden.
   */
  private function disableOverridenConfigs(array &$form) {
    global $config;
    $config_name = 'cms_content_sync.cms_content_sync.' . $form['id']['#default_value'];

    // If the default overrides aren't used check if a
    // master / subsite setting is used.
    if (!isset($config[$config_name]) || empty($config[$config_name])) {
      // Is this site a master site? It is a subsite by default.
      $environment = 'subsite';
      if ($this->configFactory->get('config_split.config_split.cms_content_sync_master')
        ->get('status')) {
        $environment = 'master';
      }
      $config_name = 'cms_content_sync.sync.' . $environment;
    }
    $fields = Element::children($form);
    foreach ($fields as $entity_type_name => $bundles) {
      if (!is_array($bundles)) {
        continue;
      }
      foreach ($bundles as $bundle_name => $bundle_config) {
        if (!is_array($bundle_config)) {
          continue;
        }
        if (empty($bundle_config['properties'])) {
          continue;
        }
        foreach ($bundle_config['properties'] as $field_name => $field_config) {
          $field_key = $entity_type_name . '-' . $bundle_name . '-' . $field_name;
          if ($this->configIsOverridden($field_key, $config_name)) {
            $form[$field_key]['#disabled'] = 'disabled';
            $form[$field_key]['#value'] = $this->configFactory->get($config_name)
              ->get($field_key);
            unset($form[$field_key]['#default_value']);
          }
        }
      }
    }
  }

  /**
   * Check if a config is overridden.
   *
   * Right now it only checks if the config is in the $config-array (overridden
   * by the settings.php)
   *
   * @param string $config_key
   *   The configuration field_name.
   * @param string $config_name
   *   The configuration name.
   *
   * @return bool
   *
   * @todo take care of overriding by modules and languages
   */
  private function configIsOverridden($config_key, $config_name) {
    global $config;

    return isset($config[$config_name][$config_key]);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc