cms_content_sync-3.0.x-dev/src/Form/PoolForm.php
src/Form/PoolForm.php
<?php
namespace Drupal\cms_content_sync\Form;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Entity\Pool;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandler;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Site\Settings;
use EdgeBox\SyncCore\Exception\SyncCoreException;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form handler for the Pool add and edit forms.
*/
class PoolForm extends EntityForm {
/**
* @var int Defines the max length for the siteID. This must be limited due to the maximum characters allowed for table names within mongo db.
*/
public const siteIdMaxLength = 20;
/**
* @var string STEP_SYNC_CORE Select a Sync Core another pool on this site already uses or enter a new one
*/
public const STEP_SYNC_CORE = 'sync-core';
/**
* @var string STEP_POOL Select pool from existing pools in the Sync Core or enter a new one
*/
public const STEP_POOL = 'pool';
/**
* @var string CONTAINER_ID The element ID of the container that's replaced with every AJAX request
*/
public const CONTAINER_ID = 'pool-form-container';
/**
* @var null|string
*/
protected $backendUrl;
/**
* @var null|string
*/
protected $configMachineName;
/**
* @var null|string
*/
protected $overwrittenSiteId;
/**
* Constructs an PoolForm object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entityTypeManager.
* @param \Drupal\Core\Extension\ModuleHandler $moduleHandler
* The moduleHandler.
*/
public function __construct(EntityTypeManagerInterface $entityTypeManager, ModuleHandler $moduleHandler) {
$this->entityTypeManager = $entityTypeManager;
$this->moduleHandler = $moduleHandler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('module_handler')
);
}
/**
* Return next form.
*
* @param array $form
*
* @return array the bundle settings
*/
public function ajaxReturn($form, FormStateInterface $form_state) {
return $form['elements'];
}
/**
* Rebuild form for next step.
*
* @param array $form
*/
public function connectSyncCore($form, FormStateInterface $form_state) {
$form_state->setRebuild();
}
/**
* Rebuild form for next step.
*
* @param array $form
*/
public function checkSiteId($form, FormStateInterface $form_state) {
$form_state->setRebuild();
}
/**
* Rebuild form for next step.
*
* @param array $form
*/
public function createNew($form, FormStateInterface $form_state) {
$form_state->setValue('id', 'new');
// If we only use ->setValue() the default value in the input element will still be whatever was selected at the radios.
$data = $form_state->getUserInput();
$data['id'] = 'new';
$form_state->setUserInput($data);
$form_state->setRebuild();
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$form['#tree'] = FALSE;
$defaults = $this->getDefaults($form_state);
if (!empty($defaults['backend_url'])) {
$form['default_backend_url'] = [
'#type' => 'hidden',
'#value' => $defaults['backend_url'],
];
}
if (!empty($defaults['name'])) {
$form['default_name'] = [
'#type' => 'hidden',
'#value' => $defaults['name'],
];
}
if (!empty($defaults['id'])) {
$form['default_id'] = [
'#type' => 'hidden',
'#value' => $defaults['id'],
];
}
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
// Check if the site id or backend_url got set within the settings*.php.
if (!is_null($pool->id)) {
$this->configMachineName = $pool->id;
$cms_content_sync_settings = Settings::get('cms_content_sync');
if (!is_null($cms_content_sync_settings) && isset($cms_content_sync_settings['pools'][$pool->id]['backend_url'])) {
$this->backendUrl = $cms_content_sync_settings['pools'][$pool->id]['backend_url'];
}
}
if (!isset($this->configMachineName)) {
$this->configMachineName = '<machine_name_of_the_configuration>';
}
$step = $this->getCurrentFormStep($form_state);
if (self::STEP_SYNC_CORE === $step) {
$elements = $this->syncCoreForm($form, $form_state);
}
else {
$elements = $this->poolForm($form, $form_state);
}
$form['elements'] = array_merge([
'#prefix' => '<div id="' . self::CONTAINER_ID . '">',
'#suffix' => '</div>',
'step' => [
'#type' => 'hidden',
'#value' => $step,
],
], $elements);
$this->entity->cleanToSerialize();
return $form;
}
/**
* Validate format of input fields and make sure the Sync Core backend is
* accessible to actually update it.
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
$step = $this->getLastFormStep($form_state);
/**
* @var \Drupal\cms_content_sync\Entity\Pool $entity
*/
$entity = $this->entity;
$entity->backend_url = $form_state->getValue('backend_url');
if (self::STEP_POOL === $step) {
$api = $form_state->getValue('id');
if (!preg_match('@^([a-z0-9\-_]+)$@', $api)) {
$form_state->setErrorByName('id', $this->t('Please only use letters, numbers and dashes.'));
}
if ('drupal' == $api || 'api-unify' == $api) {
$form_state->setErrorByName('api', $this->t('This name is reserved.'));
}
$site_id = $form_state->getValue('site_id');
}
$this->entity->cleanToSerialize();
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
if (!$pool->label()) {
$pool->label = $this->getRemotePools($form_state)[$pool->id()];
}
$status = $pool->save();
if ($status) {
if (empty(Flow::getAll())) {
$link = Link::createFromRoute(
$this->t('Create a Flow'),
'entity.cms_content_sync_flow.add_form'
)->toString();
\Drupal::messenger()->addStatus(
$this->t('Saved Pool %label. Well done! @create to continue the setup.', [
'%label' => $pool->label(),
'@create' => $link,
])
);
\Drupal::messenger()->addStatus(
$this->t('If you have connected another site already, @copy. Mirroring means you can simply swap the push and pull settings.', [
'@copy' => Link::createFromRoute('copy or mirror the configuration from another site', 'entity.cms_content_sync_flow.copy_remote')->toString(),
])
);
}
else {
\Drupal::messenger()->addStatus(
$this->t('Saved Pool %label.', [
'%label' => $pool->label(),
])
);
}
}
else {
\Drupal::messenger()->addStatus(
$this->t('The %label Pool could not be saved.', [
'%label' => $pool->label(),
])
);
}
// Make sure that the export is executed.
$destination = \Drupal::request()->query->get('destination');
\Drupal::request()->query->remove('destination');
// Keep destination for after the export.
$options = [];
if (!empty($destination)) {
$options['query']['destination'] = $destination;
}
$form_state->setRedirect('entity.cms_content_sync_pool.export', ['cms_content_sync_pool' => $this->entity->id()], $options);
$this->entity->cleanToSerialize();
}
/**
* Helper function to check whether an Pool configuration entity exists.
*
* @param mixed $id
*/
public function exist($id) {
$entity = $this->entityTypeManager->getStorage('cms_content_sync_pool')->getQuery()
->accessCheck(FALSE)
->condition('id', $id)
->execute();
return (bool) $entity;
}
/**
* Get default values that were provided in the URL.
*
* @return array
*/
protected function getDefaults(FormStateInterface $form_state) {
return [
'backend_url' => empty($_GET['backend_url']) ? $form_state->getValue('default_backend_url') : $_GET['backend_url'],
'name' => empty($_GET['name']) ? $form_state->getValue('default_name') : $_GET['name'],
'id' => empty($_GET['id']) ? $form_state->getValue('default_id') : $_GET['id'],
];
}
/**
* Return the current step of our multi-step form.
*
* @return string
*/
protected function getCurrentFormStep(FormStateInterface $form_state) {
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
if (!$pool->getSyncCoreUrl() && !$form_state->getValue('backend_url')) {
return self::STEP_SYNC_CORE;
}
return self::STEP_POOL;
}
/**
* Step 2: Enter or select site ID.
*
* @param bool $collapsed
*
* @return array the form
*/
protected function syncCoreForm(array $form, FormStateInterface $form_state, $collapsed = FALSE) {
return [];
}
/**
* Step 3: Select an existing pool or create a new one.
*
* @throws \Exception
*
* @return array the form
*/
protected function poolForm(array $form, FormStateInterface $form_state) {
$elements = $this->syncCoreForm($form, $form_state, TRUE);
$elements['headline'] = [
'#markup' => '<br><br><h1>Step 3: Pool properties</h1>',
];
/**
* @var \Drupal\cms_content_sync\Entity\Pool $pool
*/
$pool = $this->entity;
$default_name = $this->getDefaults($form_state)['name'];
$default_id = $this->getDefaults($form_state)['id'];
$options = $pool->isNew() && empty($default_id) ? $this->getRemotePools($form_state) : [];
if (count($options) && !$form_state->getValue('id')) {
$elements['id'] = [
'#type' => 'radios',
'#title' => $this->t('Existing pools'),
'#required' => TRUE,
'#default_value' => array_slice(array_keys($options), 0, 1)[0],
'#options' => $options,
];
}
else {
$elements['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#maxlength' => 255,
'#default_value' => empty($default_name) ? ($form_state->getValue('label') ? $form_state->getValue('label') :
($pool->label() ? $pool->label() : (empty(Pool::getAll()['content']) ? $this->t('Content') : ''))) : $default_name,
'#description' => $this->t("The pool name. If you aren't working with multiple pools just leave it as it is."),
'#required' => TRUE,
];
$elements['id'] = [
'#type' => 'machine_name',
'#default_value' => empty($default_id) ? ($form_state->getValue('id') ? $form_state->getValue('id') :
($pool->id() ? $pool->id() : (empty(Pool::getAll()['content']) ? 'content' : ''))) : $default_id,
'#machine_name' => [
'exists' => [$this, 'exist'],
],
'#description' => $this->t("A unique ID that must be identical on all sites that want to connect to this pool. If you aren't working with multiple pools just leave it as it is."),
'#disabled' => !$pool->isNew(),
];
}
// AJAX? Show submit buttons inline.
if ($form_state->getTriggeringElement()) {
$actions = $this->actions($form, $form_state);
$actions['submit']['#attributes']['class'][] = 'button--primary';
$elements = array_merge($elements, $actions);
}
if (!isset($elements['label'])) {
$elements['create'] = [
'#prefix' => '<br><br>',
'#type' => 'submit',
'#submit' => ['::createNew'],
'#value' => $this->t('Create new'),
'#name' => 'create',
'#ajax' => [
'callback' => '::ajaxReturn',
'wrapper' => self::CONTAINER_ID,
'method' => 'replace',
'effect' => 'fade',
'progress' => [
'type' => 'throbber',
'message' => 'loading settings...',
],
],
];
}
return $elements;
}
/**
* List all remote pools that aren't used locally yet.
*
* @throws \Exception
*/
protected function getRemotePools(FormStateInterface $form_state) {
/**
* @var \Drupal\cms_content_sync\Entity\Pool $entity
*/
$entity = $this->entity;
$client = $entity->getClient();
$pools = $client->getConfigurationService()->listRemotePools();
$local_pools = Pool::getAll();
foreach ($pools as $id => $name) {
// Already exists locally.
if (isset($local_pools[$id])) {
unset($pools[$id]);
}
}
return $pools;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$step = $this->getCurrentFormStep($form_state);
if (self::STEP_POOL !== $step) {
return [];
}
return parent::actions($form, $form_state);
}
/**
* Return the current step of our multi-step form.
*
* @return string
*/
protected function getLastFormStep(FormStateInterface $form_state) {
$step = $form_state->getValue('step');
if (empty($step)) {
return self::STEP_SYNC_CORE;
}
return $step;
}
}
