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]);
}
}
